1. <nobr id="easjo"><address id="easjo"></address></nobr>

      <track id="easjo"><source id="easjo"></source></track>
      1. 
        

      2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
      3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>
          貴州做網站公司
          貴州做網站公司~專業!靠譜!
          10年網站模板開發經驗,熟悉國內外開源網站程序,包括DEDECMS,WordPress,ZBlog,Discuz! 等網站程序,可為您提供網站建設,網站克隆,仿站,網頁設計,網站制作,網站推廣優化等服務。我們專注高端營銷型網站,企業官網,集團官網,自適應網站,手機網站,網絡營銷,網站優化,網站服務器環境搭建以及托管運維等。為客戶提供一站式網站解決方案?。?!

          LINUX系統編程-----上

          來源:互聯網轉載 時間:2023-09-09 22:38:54

          文章目錄

          • 第一章 linux系統介紹(屬于扯閑篇)
            • linux的概況
            • linux的歷史
              • 起源unix
              • Posix標準和其他標準
              • 開源運動
              • linux的誕生
            • linux使用
              • 使用范圍
              • linux的登錄
          • 第二章 linux常用命令
            • linux的shell使用
              • 切換用戶
              • 顯示所有用戶
              • 退出當前用戶
              • 添加用戶
            • 刪除用戶
            • 當前工作目錄
            • 當前工作目錄下的所有文件
            • 改變當前工作目錄
            • 創建目錄
            • 刪除目錄
            • 拷貝文件或者目錄
            • 移動文件或者目錄
            • 書目錄結構顯示
            • 修改權限
            • 掩碼
            • 這里僅僅做linux的部分介紹所以不詳細,詳細需要學習linux相關教程
            • 安裝相應的幫助手冊
          • 第三章 文件操作
            • 基于文件指針的文件操作
            • linux文件的打開與關閉
            • 關閉文件
            • fread和fwrite函數
            • 格式化讀寫
            • 目錄操作
            • 修改目錄
            • 創建和刪除文件夾
            • stat獲取文件信息
            • 基于文件描述符的文件操作
              • 文件描述符
              • 讀寫文件
              • 改變文件大小
              • mmap
              • 文件定位
              • 獲取文件信息
              • 文件描述符的復制
            • 管道
              • 堵塞狀態
              • 全雙工通信和半雙工通信
              • select的使用
          • 第四章 進程
            • 進程的產生
              • 什么是進程?
              • 虛擬
              • 優先級
            • 進程的管理
              • 進程標識符
              • 父子進程
              • 進程的用戶ID和組ID
              • 有效用戶ID和有效組ID通過函數 geteuid() 和getegid() 獲得。
            • 進程的構成
              • 虛擬內存
              • 進程地址空間
              • 內核態和用戶態
              • linux的優先級
              • kill命令
            • 系統調用
              • fork函數
              • exec函數族
            • 進程控制
              • 孤兒進程
              • 僵尸進程
              • wait和waitpid
              • 進程終止
            • 守護進程
              • 進程組
              • 守護進程的創建流程
          • 未完待續

          第一章 linux系統介紹(屬于扯閑篇)

          linux的概況

          Linux是一種免費、開源的操作系統,最初由芬蘭的Linus Torvalds在1991年開發。它是Unix-like操作系統的一種,具有高度的可定制性和靈活性。Linux操作系統包括內核和一組用戶空間程序,可以運行在各種硬件平臺上。

          Linux的特點包括安全性高、穩定性強、性能卓越、可移植性好、多用戶多任務支持等。Linux也支持多種桌面環境,如GNOME、KDE、XFCE等,用戶可以根據自己的需要進行選擇。

          目前,Linux已經成為服務器領域的主流操作系統,被廣泛用于Web服務器、數據庫服務器、云計算等領域。同時,Linux也被越來越多的個人用戶所使用,如開發人員、科學家、藝術家等。

          linux的歷史

          起源unix

          Unix 是一種操作系統,它最初由貝爾實驗室開發并于 1970 年代初開始使用。Unix 系統是多用戶、多任務和支持網絡的操作系統,被廣泛地用于服務器、工作站和移動設備等各種場合。

          Unix 操作系統有許多不同版本,包括商業版本(如 Solaris、HP-UX、AIX 和 macOS)和自由軟件版本(如 Linux 和 FreeBSD)。Unix 系統采用了分層的設計方式,其中核心(kernel)是操作系統的核心部分,負責管理硬件和提供基本服務。其他組件則往往是一些標準工具和應用程序,如 shell、編輯器、編譯器和數據庫等。Unix 系統也提供了強大的命令行界面和腳本語言,使得用戶能夠方便地進行自動化和批處理操作。

          Unix 系統的優點包括穩定性、安全性、可靠性、靈活性和可定制性。這些特點使得 Unix 系統在服務器領域得到了廣泛的應用,而且也為開發者提供了一個極好的開發平臺。

          Posix標準和其他標準

          POSIX(可移植操作系統接口)是一個被 IEEE 組織標準化的接口規范,旨在使得不同 Unix 系統間的軟件可以互通。POSIX 標準定義了許多基本操作、文件系統、進程管理、線程和 IPC 等方面的 API,以及各種環境變量和配置參數,這些都是 Unix 操作系統中常用的功能。

          除了 POSIX 標準外,還有一些其他標準對于操作系統也非常重要。其中最重要的標準之一是 C 語言的 ANSI C 標準和 ISO C 標準,它們定義了 C 語言的語法、語義、庫函數等方面的規范。由于許多操作系統都使用 C 語言編寫,因此這些標準為開發者提供了一個統一的編程接口。

          另一個重要的標準是 TCP/IP 協議族,它是 Internet 上應用最廣泛的協議族,定義了數據傳輸的各個層次的協議。操作系統需要支持 TCP/IP 協議族才能進行網絡通信和 Internet 訪問。

          此外,還有許多其他標準與操作系統相關,如編譯器前端和后端的標準、文件格式的標準、安全性標準等等。這些標準都是操作系統開發者需要知道和掌握的內容。

          開源運動

          開源運動的興起可以追溯到二十世紀九十年代,當時一些軟件開發者開始將自己編寫的代碼公開發布,并使用自由軟件許可證授權他人使用、修改和分發這些代碼。這樣做的目的是為了推廣自由軟件的理念,鼓勵更多的人貢獻自己的力量,同時也為了避免專有軟件的限制和不公平。

          隨著互聯網的普及,開源運動逐漸得到了越來越多的支持者和參與者。開源模式具有高度的靈活性和適應性,它可以在全球范圍內進行協作,吸引了大量的開發者和用戶參與其中。同時,開源軟件的質量也受到了廣泛認同,很多開源軟件已經成為商業系統中不可或缺的組成部分。

          開源運動的興起還促進了知識共享和技術創新的發展。開源社區提倡合作和交流,使得參與者可以共同學習和解決問題。通過開放的合作模式,開源項目能夠在更短的時間內獲得更多的創意和想法,這對于技術創新來說非常重要。

          總的來說,開源運動的興起是一個重要的歷史事件,它推動了軟件行業的變革,促進了自由和開放的文化氛圍,同時也為技術創新提供了更廣闊的空間。

          linux的誕生

          Linux的歷史可以追溯到1991年,當時一個名為Linus Torvalds的芬蘭大學生開始編寫一個新的操作系統內核。他的目標是開發一種類Unix的操作系統內核,能夠在他的個人計算機上運行。

          最初,Linus發布了這個內核的版本0.01,并將其上傳到互聯網上供其他人試用和改進。隨著時間的推移,越來越多的程序員參與到Linux內核的開發中,幫助完善它的功能和性能。

          Linux很快就成為了自由軟件運動的一部分,因為它是開源的并且許可證允許用戶自由地使用、修改和傳播它。隨著Linux的不斷發展,許多組織和公司開始支持和使用它。例如,Red Hat和SUSE等公司提供商業版的Linux發行版,并獲得了商業成功。

          現在,Linux已經成為世界上最流行的操作系統之一,被廣泛應用于服務器、桌面、嵌入式系統、移動設備等領域。同時,Linux社區也非?;钴S,開發出了眾多優秀的開源軟件和工具,為用戶提供了無限的可能。

          成功原因
          Linux 之所以能夠成功,有以下幾個方面的原因:

          • 開源:Linux 是一款開源操作系統,這意味著任何人都可以查看其代碼、修改和分發,這種開放性吸引了大量技術人員參與到 Linux 的開發中來。同時,開源模式還促進了知識共享和技術創新的發展。

          • 自由軟件許可證:Linux 使用自由軟件許可證(GPL),這使得用戶可以自由地使用、修改和分發 Linux 系統,而不需要支付任何費用。這種免費的授權方式吸引了大量用戶和企業采用 Linux 系統。

          • 可定制性:Linux 擁有高度的可定制性,用戶可以根據自己的需求選擇適合自己的組件和配置參數,這使得 Linux 能夠在不同的場景下得到廣泛的應用。

          • 多平臺支持:Linux 可以運行在多種硬件平臺上,包括 PC、服務器、嵌入式設備等等。這使得 Linux 成為了一種非常靈活和通用的操作系統。

          • 社區支持:Linux 擁有龐大的用戶和開發者社區。這些社區成員提供了豐富的技術資源和支持,使得 Linux 用戶可以得到及時的幫助和解決方案。

          總的來說,Linux 之所以能夠成功,是因為它具有強大的可定制性、開放的開發模式、自由軟件許可證、多平臺支持和龐大的社區支持。這些因素使得 Linux 成為了一款非常靈活、強大和受歡迎的操作系統。

          linux的版本
          Linux 是一種開放源代碼的操作系統,可以基于其內核構建各種不同版本的 Linux 操作系統。這些不同版本的 Linux 被稱為“發行版”(Distribution,縮寫為 Distro),它們通常根據用戶的需求和偏好,或特定領域的應用進行優化和定制。

          以下是幾個知名的 Linux 發行版:

        1. Ubuntu:由 Canonical 公司開發和維護的基于 Debian 的 Linux 發行版,以易用性和穩定性著稱,廣泛應用于桌面、服務器和云平臺等場景。
        2. Red Hat Enterprise Linux:由 Red Hat 公司開發和維護的商業 Linux 發行版,主要用于企業級服務器和工作站,提供高可靠性和安全性。
        3. Fedora:由社區驅動的 Linux 發行版,由 Red Hat 公司支持,以最新的軟件包和技術為特點。
        4. CentOS:由社區維護的免費 Linux 發行版,以穩定性和安全性聞名,主要用于企業級服務器和工作站。
        5. Debian:由社區開發和維護的免費 Linux 發行版,以穩定性和可靠性著稱,廣泛應用于服務器和桌面環境。
        6. Arch Linux:由社區維護的適合高級用戶的 Linux 發行版,具有高度的靈活性和可定制性,提供了最新的軟件包和最新的功能。
        7. 此外,還有很多其他的 Linux 發行版,它們都根據特定的需求或目標進行優化和定制,如 Kali Linux 用于網絡安全、Raspbian 用于樹莓派等。

          linux使用

          使用范圍

          Linux 是一種功能強大、靈活并且免費的操作系統,可以應用于各種場景和用途。以下是 Linux 的一些常見使用場景:

        8. 服務器:Linux 在服務器領域得到了廣泛應用,因為它具有高可靠性、安全性和穩定性。許多互聯網公司、數據中心和云計算服務提供商都在使用 Linux 系統。
        9. 桌面:Linux 可以作為桌面操作系統來使用,提供和其他桌面操作系統相同的功能和應用。這些桌面環境包括 GNOME、KDE Plasma、Cinnamon 等。
        10. 移動設備:Android 是基于 Linux 內核構建的移動操作系統,廣泛應用于智能手機、平板電腦等移動設備上。
        11. 嵌入式系統:Linux 還被廣泛用于嵌入式設備的控制系統、網絡設備等方面。
        12. 開發平臺:Linux 提供了豐富的開發工具和編程環境,可以作為軟件開發平臺使用,支持多種編程語言和開發框架。
        13. 總的來說,Linux 是一種非常靈活、強大的操作系統,它可以應用于各種場景和用途。無論是在服務器、桌面、移動設備、嵌入式系統還是開發平臺,Linux 都提供了高度的可定制性和靈活性,使得用戶能夠根據自己的需求選擇適合自己的發行版和應用程序。

          linux的登錄

          在 Linux 操作系統中,登錄通常有兩種方式:本地登錄和遠程登錄。

          • 本地登錄:本地登錄是指用戶直接使用計算機進行登錄。通常在啟動或重新啟動后,Linux 系統會進入命令行界面或圖形化界面,用戶需要輸入對應的用戶名和密碼進行登錄。

          • 遠程登錄:遠程登錄是指用戶通過網絡連接到遠程 Linux 計算機進行登錄??梢允褂?SSH(Secure Shell)協議來實現遠程登錄。首先確保目標計算機已安裝并啟動了 SSH 服務,在本地計算機上打開一個可用的終端窗口,輸入以下命令:

          ssh username@ip_address

          其中,username 是目標計算機上的有效用戶賬戶名,ip_address 是目標計算機的 IP 地址。然后輸入相應的密碼即可登錄到遠程 Linux 計算機。

          在登錄后,用戶可以執行各種命令來管理文件、安裝軟件和配置系統等操作。為了保證系統的安全性,用戶登錄后應該避免使用 root 賬戶直接操作系統,而是使用普通用戶賬戶進行常規操作,必要時再使用 sudo 或 su 命令獲取 root 權限。同時,用戶也應該定期更改密碼,并且只使用合法來源的軟件和命令。

          cat /etc/passwd

          cat /etc/shadow

          uname -a # 查看內核版本

          Linux jonaton-linux 5.15.0-60-generic #66-Ubuntu SMP Fri Jan 20 14:29:49 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

          第二章 linux常用命令

          由于linux基本上都是遠程鏈接使用所以要試一下網絡連接。

          ifconfig #可以看到網絡信息 ping alibaba.com#注意沒有被墻的網站可以訪問,而clash等軟件是在應用層所以并不能被ping通


          一般情況下默認安裝了ssh,所以可以pass掉安裝過程

          linux的shell使用

          由于使用ubuntu所以所有的使用,基于Ubuntu

          sudo passwd root

          切換用戶

          su user_name

          當嵌套使用su指令來依次進入多個用戶時候,多個用戶是使用 棧結構 來管理的。執行su指令相當于將
          新用戶壓入棧頂,執行exit指令相當于彈出棧頂

          顯示所有用戶

          $cat /etc/passwd

          退出當前用戶

          exit

          添加用戶

          useradd 用戶名

          只有root用戶或者擁有sudo權限的用戶使用sudo命令后才能添加用戶

          #給useradd命令添加參數,在用戶名之前使用-m,可以為用戶添加默認家目錄(如果不添加家目錄,這個用戶將無法創建文件)。 # 添加用戶并指定家目錄 $ useradd -m 用戶名 -s /bin/bash #執行完命令,先使用pwd命令獲取當前工作目錄,使用cd命令進入/home目錄,再使用ls命令顯示當前 #目錄下的所有文件。就會發現home的下面新建了一個新的目錄,目錄的名字就是用戶名,這里就是新用戶的家目錄 pwd cd /home


          刪除用戶

          $userdel 用戶名如果用戶正在使用中,那么這個用戶就不能被刪除 如果用戶在su命令和exit命令構成的用戶 棧結構 當中的時候,那么這個用戶也不能被刪除 在userdel后面添加-r選項,可以刪除用戶家目錄下的文件 $userdel -r 用戶名


          當前工作目錄

          pwd

          當前工作目錄下的所有文件

          lsls 工作路徑

          改變當前工作目錄

          cd

          創建目錄

          mkdir 目錄名

          刪除目錄

          rmdir 目錄名如果文件夾中由文件 rmdir -r 目錄名

          拷貝文件或者目錄

          cp [選項] 源文件 目標路徑|目標文件cp -r dir1 path

          移動文件或者目錄

          mv [選項] 源文件 目標路徑|目標文件

          書目錄結構顯示

          一般情況下沒有安裝tree
          所以

          sudo apt install tree 使用tree命令就可以顯示目錄的樹狀結構 $tree 路徑名

          修改權限

          在 Linux 中,可以使用命令 chmod 來修改文件或目錄的權限。 chmod 命令需要指定三個數字參數來表示文件的權限:用戶權限、組權限和其他人權限。

          每個數字參數都是由三個數字組成的,分別代表讀(r)、**寫(w)執行(x)**這三種權限。每種權限用一個二進制位表示,讀權限用1表示,寫權限用2表示,執行權限用4表示。因此,一個數字參數就是將這三種權限對應的二進制位相加得到的。

          例如,數字參數為 7 表示所有權限都被打開,即 rwx;數字參數為 6 表示讀和寫權限被打開,即 rw-。

          在 Linux 中,可以使用兩種方式來修改文件或目錄的權限:文字設定法和數字設定法。

          文字設定法是指通過符號來表示權限。具體地說,可以使用 u、g 和 o 分別表示用戶、組和其他人,并且可以用 +、- 和 = 來分別表示添加、刪除和設置權限。例如:

          # 將 file.txt 的執行權限授予當前用戶 chmod u+x file.txt# 將 dir 目錄及其所有子目錄和文件的讀取、寫入和執行權限授予用戶和組,但不授予其他人 chmod g+rwx,u+rwx,o-rwx -R dir/

          數字設定法是指通過數字來表示權限。具體地說,可以使用三個八進制數來表示用戶、組和其他人的權限,其中每個八進制數由三個二進制位來表示讀、寫和執行權限。例如:

          # 將 file.txt 的所有權限都授予用戶和組,但不授予其他人 chmod 660 file.txt# 將 dir 目錄及其所有子目錄和文件的讀取、寫入和執行權限授予所有用戶 chmod 777 -R dir/

          注意,在數字設定法中,每個八進制數所代表的權限是固定的,無法靈活地進行單獨的權限修改。因此,相對來說,文字設定法更加直觀和易于理解。

          掩碼

          在 Linux 中,還有一種與文件和目錄權限相關的概念叫做掩碼(umask)。掩碼是一個八進制數字,用來指定新創建的文件或目錄應該限制哪些權限。具體地說,掩碼中每個二進制位代表一個特定的文件權限(讀、寫、執行),如果對應位上的數值為 0,則表示新文件或目錄會保留該權限,反之則表示該權限會被限制。

          例如,如果掩碼設置為 022,則新創建的文件的權限為 rw-r--r--,新創建的目錄權限為 rwxr-xr-x。這是因為在 Linux 中,新建的文件會自動繼承創建它的目錄的權限,但同時會受到掩碼的限制,即默認會關閉掉執行權限。同樣地,新建的目錄也會繼承創建它的目錄的權限,并且默認會開啟所有權限。

          要查看當前系統的掩碼設置,可以使用 umask 命令。要修改掩碼設置,可以直接使用 umask 命令加上合適的參數,例如:

          # 將掩碼設置為 027,即新建的文件只有用戶有讀取和執行權限,組和其他人無任何權限 umask 027

          需要注意的是,掩碼不影響已經存在的文件和目錄的權限,只會影響新創建的文件和目錄。如果需要修改已有文件或目錄的權限,需要使用 chmod 命令進行修改。

          這里僅僅做linux的部分介紹所以不詳細,詳細需要學習linux相關教程

          安裝相應的幫助手冊

          $ sudo apt install manpages-posix-dev

          第三章 文件操作

          基于文件指針的文件操作

          Linux 是一個基于 Unix 的操作系統,它使用一種樹形目錄結構來組織文件。在 Linux 中,所有的文件都位于根目錄下的一個文件系統中,并且可以通過路徑名來訪問。

          Linux 中的文件可以分為幾類:

          • 普通文件:這是最常見的一種文件類型,包括文本文件、二進制文件等。

          • 目錄文件:目錄文件是一種特殊的文件類型,它包含了其他文件或目錄的列表。

          • 設備文件:設備文件是用于訪問硬件設備的文件,例如磁盤驅動器、打印機等。

          • 符號鏈接文件:符號鏈接文件是指向其他文件或目錄的文件,類似于 Windows 中的快捷方式。

          • 套接字文件:套接字文件用于進行進程間通信。

          • 管道文件:管道文件也用于進程間通信,但只能支持單向通信。

          除了以上文件類型之外,Linux 中還有一些其他的特殊文件類型,例如權限文件、FIFO 文件等。

          linux文件的打開與關閉

          //所需頭文件和相應的參數 SYNOPSISSYNOPSIS#include <stdio.h>FILE *fopen(const char *pathname, const char *mode);FILE *fdopen(int fd, const char *mode);FILE *freopen(const char *pathname, const char *mode, FILE *stream);Feature Test Macro Requirements for glibc (see feature_test_macros(7)):fdopen(): _POSIX_C_SOURCE

          mode的模式


          打開成功時候返回一個文件指針

          函數fopen()中的mode參數用于指定文件的打開模式。以下是常見的模式選項:

          • “r” 只讀方式打開文件,該文件必須已經存在。
          • “w” 寫入方式打開文件,如果文件不存在則會創建該文件,如果文件已經存在則將文件內容清空。
          • “a” 追加方式打開文件,如果文件不存在則會創建該文件,如果文件已經存在則在文件末尾追加內容。
          • “r+” 讀寫方式打開文件,該文件必須已經存在。
          • “w+” 讀寫方式打開文件,如果文件不存在則會創建該文件,如果文件已經存在則將文件內容清空。
          • “a+” 讀寫方式打開文件,如果文件不存在則會創建該文件,如果文件已經存在則在文件末尾追加內容。

          在這些模式選項后面還可以添加“b”字符,以表示二進制模式。例如,“rb”表示以只讀方式打開一個二進制文件。

          關閉文件

          //關閉文件需要的頭文件和參數 SYNOPSIS#include <stdio.h>int fclose(FILE *stream);

          fread和fwrite函數

          SYNOPSIS#include <stdio.h>size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
          • 第一個參數是一個指向要寫入數據的內存塊的指針。
          • 第二個參數是每個數據項的字節數。
          • 第三個參數是要寫入的數據項數。
          • 第四個參數是指向FILE結構體的指針,表示要寫入的文件。

          格式化讀寫

          #include <stdio.h> int printf(const char *format, ...); //相當于fprintf(stdout,format,…); int scanf(const char *format, …); int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, …); int sprintf(char *str, const char *format, ...); //eg:sprintf(buf,”the string is;%s”,str); int sscanf(char *str, const char *format, …);

          fprintf將格式化后的字符串寫入到文件流stream中
          sprintf將格式化后的字符串寫入到字符串str中

          char *fgets(char *s, int size, FILE *stream); int fputs(const char *s, FILE *stream); int puts(const char *s);//等同于 fputs(const char *s,stdout); char *gets(char *s);//等同于 fgets(const char *s, int size, stdin);
          • fgets和fputs從文件流stream中讀寫一行數據;
          • puts和gets從標準輸入輸出流中讀寫一行數據。
          • fgets可以指定目標緩沖區的大小,所以相對于gets安全,但是fgets調用時,如果文件中當前行的
          • 字符個數大于size,則下一次fgets調用時,將繼續讀取該行剩下的字符,fgets讀取一行字符時,保留行尾的換行符。
          • fputs不會在行尾自動添加換行符,但是puts會在標準輸出流中自動添加一換行符。
          • 文件定位:文件定位指讀取或設置文件當前讀寫點,所有的通過文件指針讀寫數據的函數,都是從文件的當前
            讀寫點讀寫數據的。常用的函數有:
          #include <stdio.h> int feof(FILE * stream); //通常的用法為while(!feof(fp)),沒什么太多用處 int fseek(FILE *stream, long offset, int whence); //設置當前讀寫點到偏移whence 長度為offset處 long ftell(FILE *stream); //用來獲得文件流當前的讀寫位置 void rewind(FILE *stream); //把文件流的讀寫位置移至文件開頭 fseek(fp, 0, SEEK_SET); #include <sys/stat.h> int chmod(const char* path, mode_t mode); //mode形如:0777 是一個八進制整型 //path參數指定的文件被修改為具有mode參數給出的訪問權限。

          目錄操作

          獲取、改變當前目錄

          //獲取當前目錄 char *getcwd(char *buf, size_t size);char *getwd(char *buf);char *get_current_dir_name(void);
          • getcwd()` 是一個函數,它的作用是獲取當前工作目錄(Current Working Directory),即程序當前所在的目錄路徑。

            在使用 getcwd() 函數時,需要包含頭文件 <unistd.h>。該函數的原型如下:

            Copy Codechar *getcwd(char *buf, size_t size);

            其中,buf 參數是一個指向存儲路徑名的緩沖區的指針,size 參數表示緩沖區大小。如果 buf 參數為 NULL,則 getcwd() 會自動分配一個適當大小的緩沖區。調用成功后,buf 指向包含當前工作目錄的字符串,返回值為 buf。

          #include <stdio.h> #include <unistd.h>int main() {char buf[1024];if (getcwd(buf, sizeof(buf)) != NULL) {printf("Current working directory: %s\n", buf);} else {perror("Error");return 1;}return 0; }

          修改目錄

          #include <unistd.h>int chdir(const char *path);

          創建和刪除文件夾

          #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int mkdir(const char *pathname, mode_t mode); //創建目錄,mode是目錄權限 int rmdir(const char *pathname); //刪除目錄

          目錄的存儲原理

          • Linux的文件系統采用了一種樹形結構,稱為“目錄樹”或者“文件系統層次結構”,它由許多的目錄和文件組成,每個目錄都可以包含其他目錄和文件,形成了一個分層的結構。

            在Linux中,所有的目錄和文件都存儲在虛擬文件系統(Virtual File System,VFS)中。這個虛擬文件系統將硬件和文件系統之間進行了抽象,使得不同的文件系統可以使用相同的接口操作。

            Linux文件系統的目錄結構是以根目錄(/)作為起點的,所有的目錄都是從根目錄開始展開的。例如,/usr/bin表示usr目錄下的bin子目錄。在Linux中有一些特殊的目錄,如下:

          • /:根目錄,所有的文件和目錄都是從這里開始。
          • /bin:存放系統命令(binary)的目錄。
          • /sbin:存放系統管理員使用的命令(system binary)。
          • /etc:存放系統配置文件(configuration)的目錄。
          • /dev:存放設備文件(device)的目錄。
          • /proc:存放進程信息(process)的目錄。
          • /var:存放系統運行時需要變化的文件(variable)的目錄。
          • /home:存放用戶主目錄(homedir)的目錄。
          • 在Linux中,所有的目錄和文件都有權限控制,可以設置哪些用戶或組可以訪問它們。此外,因為Linux是支持多用戶的操作系統,每個用戶都有自己的主目錄,這樣不同的用戶之間可以互相隔離,保證了系統的安全性。

          struct __dirstream{void *__fd;char *__data;int __entry_data;char *__ptr;int __entry_ptr;size_t __allocation;size_t __size;__libc_lock_define (, __lock)}; typedef struct __dirstream DIR;

          在 Linux 中,你可以使用 C 語言標準庫中的函數來管理和操作文件目錄。以下是一些常見的函數及其參數:

        14. opendir() 函數:

          DIR *opendir(const char *name);

          參數:

          • name:要打開的目錄的路徑名。

          返回值:

          • 如果打開目錄成功,則返回一個指向 DIR 類型結構體的指針。
          • 如果出錯,則返回 NULL,并設置相應的錯誤碼(通過 errno 變量獲取)。
        15. readdir() 函數:

          struct dirent *readdir(DIR *dirp);

          參數:

          • dirp:先前由 opendir() 打開的目錄指針。

          返回值:

          • 如果讀取下一個目錄項成功,則返回一個指向 dirent 類型結構體的指針。
          • 如果已到達目錄末尾或出現錯誤,則返回 NULL,并設置相應的錯誤碼(通過 errno 變量獲取)。
        16. closedir() 函數:

          int closedir(DIR *dirp);

          參數:

          • dirp:先前由 opendir() 打開的目錄指針。

          返回值:

          • 如果成功關閉目錄,則返回 0。
          • 如果出錯,則返回 -1,并設置相應的錯誤碼(通過 errno 變量獲取)。
        17. mkdir() 函數:

          int mkdir(const char *path, mode_t mode);

          參數:

          • path:要創建的目錄路徑名。
          • mode:新目錄的權限和屬性??梢允褂?chmod() 函數修改它。

          返回值:

          • 如果成功創建目錄,則返回 0。
          • 如果出錯,則返回 -1,并設置相應的錯誤碼(通過 errno 變量獲取)。
        18. rmdir() 函數:

          int rmdir(const char *pathname);

          參數:

          • pathname:要刪除的目錄路徑名。

          返回值:

          • 如果成功刪除目錄,則返回 0。
          • 如果出錯,則返回 -1,并設置相應的錯誤碼(通過 errno 變量獲取)。
        19. chdir() 函數:

          int chdir(const char *path);
        20. 參數:- `path`:要更改為的目錄路徑名。返回值:- 如果成功更改當前工作目錄,則返回 0。- 如果出錯,則返回 -1,并設置相應的錯誤碼(通過 `errno` 變量獲取)。7. `getcwd()` 函數:

          char *getcwd(char *buf, size_t size);

          參數:- `buf`:用于存儲當前工作目錄的緩沖區指針。 - `size`:緩沖區大小。返回值:- 如果成功獲取當前工作目錄,則返回指向緩沖區的指針。 - 如果出錯,則返回 NULL,并設置相應的錯誤碼(通過 `errno` 變量獲取)。8. `rename()` 函數: rename(const char *oldpath, const char *newpath); 參數:- `oldpath`:要重命名的文件或目錄的路徑名。 - `newpath`:新的文件或目錄的路徑名。返回值:- 如果成功重命名文件或目錄,則返回 0。 - 如果出錯,則返回 -1,并設置相應的錯誤碼(通過 `errno` 變量獲取)。**下面例子是深度遍歷訪問目錄的例子** ```c//使用深度優先遍歷訪問目錄的例子 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h>void listdir(const char *name, int indent, int depth) {if (depth <= 0) {printf("%*s[%s]\n", indent, "", "...");return;}DIR *dir = opendir(name);if (!dir) {fprintf(stderr, "Error: Cannot open directory '%s'\n", name);return;}struct dirent *entry;while ((entry = readdir(dir)) != NULL) {if (entry->d_type == DT_DIR) {if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)continue;printf("%*s└─ %s/\n", indent, "", entry->d_name);char path[1024];snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);listdir(path, indent + 2, depth - 1);} else {printf("%*s└─ %s\n", indent, "", entry->d_name);}}closedir(dir); }int main(int argc, char **argv) {if (argc != 2) {fprintf(stderr, "Usage: %s <directory>\n", argv[0]);return 1;}printf("目錄:%s\n", argv[1]);listdir(argv[1], 0, 3); // 限制遞歸深度為 3 層return 0; }

          stat獲取文件信息

          #include <stdio.h> #include <sys/stat.h> #include <unistd.h>int main() {char* filename = "your_file_path";struct stat file_stat;if(stat(filename, &file_stat) != 0) {perror("Error in getting file stat");return -1;}printf("File stat for %s\n", filename);printf("-----------------------------------\n");printf("Mode: %o\n", file_stat.st_mode);printf("Inode number: %lu\n", file_stat.st_ino);printf("Device ID: %lu\n", file_stat.st_dev);printf("Number of hard links: %lu\n", file_stat.st_nlink);printf("UID of owner: %d\n", file_stat.st_uid);printf("GID of owner: %d\n", file_stat.st_gid);printf("Size in bytes: %ld\n", file_stat.st_size);printf("Last access time: %ld\n", file_stat.st_atime);printf("Last modification time: %ld\n", file_stat.st_mtime);printf("Last status change time: %ld\n", file_stat.st_ctime);return 0; }

          基于文件描述符的文件操作

          文件描述符

          文件描述符是一個用來標識打開的文件或者I/O設備的整數值。在Unix和類Unix操作系統中,所有的輸入/輸出設備都被看作是文件,包括終端、磁盤文件、網絡套接字等等。當應用程序需要讀取或寫入這些設備時,它們會使用文件描述符來標識要讀寫的設備。文件描述符在應用程序中通常是通過open()、socket()等函數調用獲得的。在程序讀寫設備完畢后,應該關閉文件描述符以釋放資源。

          在Unix和類Unix操作系統中,打開、創建和關閉文件通常是通過系統調用來完成的。下面是三個常用的系統調用:

        21. 打開文件:open()系統調用可以用于打開一個已經存在的文件或者創建一個新文件。它的原型如下:
        22. int open(const char *pathname, int flags);

          pathname參數指定了要打開的文件的路徑名,flags參數指定了打開文件時的選項,比如是否只讀、是否追加等。

        23. 創建文件:可以使用creat()系統調用來創建一個新的空文件。它的原型如下:
        24. int creat(const char *pathname, mode_t mode);

          pathname參數指定了要創建的文件的路徑名,mode參數指定了文件權限。

        25. 關閉文件:關閉已經打開的文件可以使用close()系統調用。它的原型如下:
        26. int close(int fd);

          fd參數為要關閉的文件描述符。

          下面是一個例子,演示如何打開、寫入內容并關閉文件:

          #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>int main() {char buf[1024];int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");exit(1);}write(fd, "Hello, world!\n", 14);close(fd);fd = open("test.txt", O_RDONLY);if (fd == -1) {perror("open");exit(1);}int n = read(fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, n);close(fd);return 0; }

          在這個例子中,我們通過open()系統調用創建了一個名為test.txt的文件,并且以只寫、創建和截斷的方式打開它。然后我們使用write()函數向文件中寫入了Hello, world!這個字符串,并最終使用close()關閉文件。

          • 執行成功時,open函數返回一個文件描述符,表示已經打開的文件;執行失敗是,open函數返回-1,并設置相應的errno
          • flags和mode都是一組掩碼的合成值,flags表示打開或創建的方式,mode表示文件的訪問權限。
          • flags的可選項有

          讀寫文件

          //用文件描述符 #include <stdio.h>int main() {char str[100];FILE *fp;// 打開文件,如果文件不存在則創建一個新文件fp = fopen("test.txt", "w+");if (fp == NULL) {printf("無法打開文件\n");return 1;}// 向文件寫入數據fprintf(fp, "這是一行文字\n");// 按行讀取文件內容并輸出到屏幕printf("文件內容為:\n");while (fgets(str, 100, fp) != NULL) {printf("%s", str);}// 關閉文件fclose(fp);return 0; } //用read write函數的程序 #include <stdio.h> #include <fcntl.h> #include <unistd.h>int main() {char buffer[1024];int fd, count;// 打開文件,如果文件不存在則創建一個新文件fd = open("test.txt", O_RDWR | O_CREAT, 0666);if (fd == -1) {printf("無法打開文件\n");return 1;}// 向文件寫入數據write(fd, "這是一行文字\n", 14);// 將文件指針移動到開始位置lseek(fd, 0, SEEK_SET);// 按字節讀取文件內容并輸出到屏幕printf("文件內容為:\n");while ((count = read(fd, buffer, sizeof(buffer))) > 0) {write(STDOUT_FILENO, buffer, count);}// 關閉文件close(fd);return 0; }

          改變文件大小

          #include <unistd.h> int ftruncate(int fd, off_t length);

          ftruncate() 是一個函數,用于截斷文件大小為指定的長度。在使用 ftruncate() 函數時,需要指定一個文件描述符和希望將該文件截斷至的新長度。如果新長度比文件的當前長度小,則文件內容將被截斷到新長度為止。如果新長度比文件的當前長度大,則文件的大小將增加,并且新增部分將被清零。該函數通常用于縮小或清空日志文件等。

          以下是 ftruncate() 函數的語法:

          #include <unistd.h>int ftruncate(int fd, off_t length);

          其中,fd 是要操作的文件描述符,length 是要設置的新文件長度,off_t 類型表示長度的數據類型。

          如果 ftruncate() 調用成功,則返回值為 0;否則返回 -1,并設置相應的錯誤代碼

          #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h>int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s file\n", argv[0]);exit(EXIT_FAILURE);}int fd = open(argv[1], O_WRONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}printf("fd = %d\n", fd);if (ftruncate(fd, 3) == -1) {perror("ftruncate");exit(EXIT_FAILURE);}close(fd);return EXIT_SUCCESS; }

          mmap

          mmap是一種在內存映射文件和設備的Unix和Unix-like操作系統中使用的系統調用。它允許進程將一個文件或設備映射到它的虛擬地址空間中,從而使得進程可以像訪問內存一樣訪問該文件或設備。

          mmap函數可以將一個文件或設備映射到調用進程的地址空間中,并返回一個指向映射區域的指針。通過這個指針,進程可以直接訪問這個文件或設備上的數據,就好像這些數據已經被讀入內存一樣。當進程訪問映射區域時,操作系統會自動將所需的數據從文件或設備中讀取到內存中,因此可以避免頻繁的磁盤I/O操作。

          此外,mmap還支持對映射區域進行讀寫鎖定、設置訪問權限、共享內存等操作,因此在實現多進程通信和共享數據時非常有用。

          使用mmap函數經常配合函數ftruncate來擴大文件大小

          #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h>int main(int argc, char *argv[]) {// 檢查參數數量是否正確if (argc != 2) {printf("Usage: %s <filename>\n", argv[0]);exit(EXIT_FAILURE);}// 打開文件int fd = open(argv[1], O_RDWR);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}printf("fd = %d\n", fd);// 設置文件大小為5字節if (ftruncate(fd, 5) == -1) {perror("ftruncate");exit(EXIT_FAILURE);}// 將文件映射到內存中char *p;p = (char *)mmap(NULL, 5, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);if (p == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}// 在映射的內存中寫入字符串結束符p[5] = 0;// 輸出映射的內存中的內容printf("%s\n", p);// 修改映射的內存中的內容,將第一個字符改為'H'p[0] = 'H';// 取消內存映射if (munmap(p, 5) == -1) {perror("munmap");exit(EXIT_FAILURE);}// 關閉文件描述符if (close(fd) == -1) {perror("close");exit(EXIT_FAILURE);}return 0; }

          文件定位

          文件定位是指在文件中準確定位到某個位置的過程。在計算機中,文件通常以二進制形式存儲,并且可以通過文件指針來訪問這些數據。文件指針是一個指向文件內部位置的變量,在讀取或寫入文件時,它會跟蹤當前位置。

          文件定位可以使用各種方法實現,其中最常用的方法是使用偏移量。偏移量是一個表示要移動多少字節的整數值,可以相對于當前位置或文件的開頭或結尾。

          文件定位還可以使用搜索方法來實現。搜索方法會在文件中查找特定的數據或字符串,并返回其位置。

          文件定位在許多場合下都是非常重要的,如在讀取大型文件時,需要準確地讀取文件中的某一部分,或者在向文件中插入數據時,需要將指針定位到正確的位置。

          lseek() 函數是用于在文件中進行定位的系統調用函數,它可以通過改變文件指針來實現文件定位。在 POSIX 標準中,lseek() 函數的原型定義如下:

          #include <sys/types.h> #include <unistd.h>off_t lseek(int fd, off_t offset, int whence);

          其中,參數 fd 是打開文件的文件描述符,offset 是要移動的偏移量,whence 則表示相對位置。其中 whence 可以取以下三個值之一:

          • SEEK_SET: 從文件開始處計算偏移量。
          • SEEK_CUR: 從當前位置計算偏移量。
          • SEEK_END: 從文件末尾處計算偏移量。

          如果操作成功,則返回新的文件指針位置,否則返回 -1 表示出錯,并設置 errno 變量來指示錯誤類型。通常情況下,文件指針的起始位置為文件開頭,也就是說,第一次調用 lseek() 函數時,whence 參數應該使用 SEEK_SET 值。

          lseek() 函數一般用于處理大型文件,比如音頻、視頻和數據庫等,可以快速跳過不需要的數據,或者精確地讀取特定位置的數據。

          #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h>int main(int argc, char *argv[]) {if (argc < 2) { // 判斷命令行參數是否足夠printf("Usage: %s <filename>\n", argv[0]);return 1;}int fd = open(argv[1], O_RDWR); // 打開指定文件,以讀寫模式打開if (fd == -1) { // 錯誤檢測和處理perror("open");return 1;}off_t ret = lseek(fd, 5, SEEK_SET); // 移動文件指針到第5個字節printf("pos = %ld\n", ret); // 輸出當前文件指針的位置char buf[128] = {0}; // 定義緩沖區ssize_t nread = read(fd, buf, sizeof(buf)); // 讀取文件內容到緩沖區中if (nread == -1) { // 錯誤檢測和處理perror("read");return 1;}printf("buf = %s\n", buf); // 輸出讀取到的文件內容close(fd); // 關閉文件return 0; // 返回程序結束狀態碼 }

          獲取文件信息

          #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *file_name, struct stat *buf); //文件名 stat結構體指針 int fstat(int fd, struct stat *buf); //文件描述詞 stat結構體指針

          文件描述符的復制

          #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd);

          dup()和dup2()是UNIX和類UNIX操作系統中的函數,用于復制文件描述符。這些函數允許進程將一個文件描述符復制到另一個文件描述符,以便在讀取或寫入文件時使用多個文件描述符。

          dup()函數會復制指定的文件描述符,并返回一個新的文件描述符,該文件描述符與原始文件描述符引用相同的打開文件。如果成功,它將返回新的文件描述符,如果失敗,則返回-1。下面是dup()函數的語法:

          #include <unistd.h> int dup(int oldfd);

          其中,oldfd 是要復制的原始文件描述符。

          而dup2()函數與dup()函數類似,但允許顯式地指定新的文件描述符。如果新文件描述符已經打開,則會先關閉其對應的文件。下面是dup2()函數的語法:

          #include <unistd.h> int dup2(int oldfd, int newfd);

          其中,oldfd 是要復制的原始文件描述符,newfd 是新的文件描述符。

          總之,dup()和dup2()函數是UNIX和類UNIX操作系統中非常常用的系統調用,它們可用于實現各種不同類型的I/O操作,例如將數據從一個文件描述符發送到另一個文件描述符,或者在創建子進程時重定向標準輸入/輸出流。

          fileno()函數


          文件描述符(File Descriptor)和文件指針(File Pointer)都是用于在程序中進行文件操作的概念,但是它們有著不同的含義和作用。

          文件描述符是一個整數,由操作系統內核分配給已打開的文件,并用于標識該文件。文件描述符通常是非負整數,其值與文件在操作系統中的位置相關聯,可以用于在程序中進行底層的文件讀寫操作,如使用read、write等函數。在Linux系統中,0、1、2分別代表標準輸入、標準輸出和標準錯誤輸出的文件描述符。

          文件指針是一個指向FILE類型結構體的指針,由C標準庫提供并維護。通過文件指針,我們可以對文件進行高層次的操作,如使用fread、fwrite等函數進行二進制文件的讀寫,或者使用fgets、fprintf等函數進行文本文件的讀寫。

          因此,文件描述符和文件指針的區別在于,文件描述符是由操作系統內核維護的底層概念,而文件指針是由C標準庫封裝的高層概念。在進行文件操作時,需要根據具體的需求選擇合適的方式。


          fileno函數是一個C標準庫函數,它的作用是獲取文件流所對應的文件描述符。文件描述符是操作系統內核用于標識已打開文件的標識符,可以用于在程序中進行文件操作。

          在本程序中,fp是使用fopen函數打開文件后返回的文件指針,類型為FILE *。而我們需要獲取文件描述符,以便進行底層的文件讀寫操作,因此調用了fileno函數將其轉換為文件描述符類型并存儲在fd變量中。

          具體的語句為int fd = fileno(fp);,其中fp表示要進行轉換的文件指針,fileno(fp)表示將文件指針轉換為文件描述符,并將結果賦值給fd變量。

          管道

          • (有名)管道文件是用來數據通信的一種文件,它是半雙工通信,它在ls -l命令中顯示為p,它不能存儲數據
          mkfifo p

          寫兩個文件來讀寫

          #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>int main() {int fd;char * data = "Hello, World!";// 打開管道文件p進行寫入fd = open("p", O_WRONLY);// 向管道中寫入數據write(fd, data, sizeof(data));// 關閉管道close(fd);return 0; } #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>int main() {int fd;char buffer[1024];// 打開管道文件p進行讀取fd = open("p", O_RDONLY);// 從管道中讀取數據read(fd, buffer, sizeof(buffer));// 輸出讀取到的數據printf("%s", buffer);// 關閉管道close(fd);return 0; }

          堵塞狀態

          某個模式下,read函數如果不能從文件中讀取內容,就將進程的狀態切換到阻塞狀態,不再繼續執行

          全雙工通信和半雙工通信

          由于管道實現的是半雙工通信,所以實現兩個程序之間通信就需要兩個管道。當多個實現通信時候,所需要的管道數目就需要集合倍增加。

          I/O多路轉接模型是一種基于事件驅動的網絡編程模型,用于實現高效的I/O多路復用。它通過操作系統提供的API(如epoll、kqueue等)來同時監控多個文件描述符(如socket),并在有事件發生時通知應用程序進行相應的處理,從而避免了創建多個線程或進程的開銷。

          I/O多路復用是指在一個進程中,同時監聽多個文件描述符上的I/O事件。當其中任意一個描述符就緒時,就可以對其進行讀取或寫入操作,從而實現高效的I/O處理。常見的I/O多路復用技術有select、poll和epoll等。

          相對于select和poll,epoll具有更高的性能和可擴展性。其主要優勢在于:

        27. 支持較大數量的文件描述符:epoll可以同時監聽數以百萬計的文件描述符,而且隨著監聽數目的增加,其性能不會隨之下降;
        28. 高效:epoll使用回調函數機制,當有事件發生時,只需要將事件信息存儲在內核空間,然后通知應用程序即可,無需像select或poll那樣每次都去遍歷所有文件描述符;
        29. 支持水平觸發和邊緣觸發兩種模式:水平觸發模式下,只要緩沖區中還有數據,內核就會一直通知應用程序;邊緣觸發模式下,只有在緩沖區狀態變化時才通知應用程序,可以減少不必要的通知。
        30. 總之,I/O多路轉接模型是一種高效、可靠的網絡編程方式,通過使用操作系統提供的API,可以實現同時監聽多個文件描述符的I/O事件,從而優化系統性能。

          select的使用

          多路轉接模型(Multiplexing)和select都是用于實現I/O多路復用的技術。

          I/O多路復用是指通過一種機制,使得一個進程能同時監聽多個文件描述符(socket或文件)。這樣,在有多個連接需要處理時,就可以使用單線程處理它們,從而避免了創建多個線程或進程的開銷。

          其中,多路轉接模型是一種基于事件驅動的模型,它利用操作系統提供的API(如epoll、kqueue等)來監控多個文件描述符,并在有事件發生時通知應用程序進行相應的處理。

          而select則是一種比較早期的I/O多路復用技術,它通過輪詢方式不斷檢查文件描述符是否就緒,如果有就緒的文件描述符,則立即返回。相對于多路轉接模型,select存在效率低下、支持文件描述符數量受限等問題,但仍然廣泛用于各種平臺和語言的網絡編程中。

          總之,多路轉接模型和select都是為了實現I/O多路復用而存在的技術,但多路轉接模型通常被認為是更加高效和可靠的選擇。

          int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);nfds:需要監聽的最大文件描述符加1; readfds:讀事件集合,包含要監聽的讀事件的文件描述符; writefds:寫事件集合,包含要監聽的寫事件的文件描述符; exceptfds:異常事件集合,包含要監聽的異常事件的文件描述符; timeout:超時時間,如果在指定時間內沒有事件發生,則退出select函數。

          select函數的返回值為就緒文件描述符的數量(即上述三個集合中有事件發生的描述符數量)。如果在超時時間內沒有事件發生,返回0;如果出錯,返回-1。

          使用select函數的主要缺點是其效率較低,因為每次調用select都需要遍歷所有的文件描述符集合,檢查是否有事件發生。另外,select支持的最大文件描述符數量存在限制,致使其不能很好地應對超大規模的高并發場景。

          //readset、writeset、exceptionset都是fd_set集合 //集合的相關操作如下: void FD_ZERO(fd_set *fdset); /* 將所有fd清零 */ void FD_SET(int fd, fd_set *fdset); /* 增加一個fd */ void FD_CLR(int fd, fd_set *fdset); /* 刪除一個fd */ int FD_ISSET(int fd, fd_set *fdset); /* 判斷一個fd是否有設置 * //簡單例子 #include <stdio.h> #include <sys/select.h>int main() {fd_set rfds;struct timeval tv;int retval;/* 需要監視的文件描述符集合 */FD_ZERO(&rfds);FD_SET(0, &rfds); // 標準輸入FD_SET(1, &rfds); // 標準輸出FD_SET(2, &rfds); // 標準錯誤輸出/* 等待的時間 */tv.tv_sec = 5;tv.tv_usec = 0;/* 監視文件描述符的狀態 */retval = select(3, &rfds, NULL, NULL, &tv);if (retval == -1){perror("select()");}else if (retval){printf("Data is available now.\n");}else{printf("No data within five seconds.\n");}return 0; }

          第四章 進程

          進程的產生

          單批次處理系統一次只能處理一個任務,例如打印一份文檔或者運行一個程序。這種處理方式通常被用于早期的計算機系統中,因為那些計算機資源有限,不能同時處理多個任務。

          多批次處理系統可以同時處理多個任務,但這些任務需要按照特定的順序進行排隊。這種處理方式通常用于大型計算機系統中,比如服務器和超級計算機等,它們可以同時處理多個任務,但仍然需要按照順序進行排隊。

          分布式處理系統允許多臺計算機在網絡上共同協作處理任務,每臺計算機都可以獨立地執行部分任務,然后將結果合并。這種處理方式通常用于大規模計算、數據處理和存儲等應用場景,因為它可以在不同的計算機之間進行負載均衡,提高整體性能和可靠性。
          分時操作系統是一種多用戶、多任務的計算機操作系統,它允許多個用戶在同一時間共享一臺計算機,并能夠同時運行多個程序。

          分時操作系統將計算機資源(如 CPU、內存、I/O 設備等)進行切片,每個用戶都被分配到一定的資源,并且每個用戶可以同時使用這些資源。這種方式不僅提高了計算機的利用率,還使得多個用戶可以在同一時間內使用計算機,從而實現了多任務處理。

          在分時操作系統中,每個用戶都有一個獨立的終端,用戶可以通過終端與操作系統進行交互,執行各種命令和程序。操作系統會根據用戶輸入的命令和程序進行調度,并將執行結果返回給用戶。

          進程的產生是由于操作系統需要管理多個任務同時運行的需求。在早期的計算機系統中,一次只能執行一個程序,當一個程序正在運行時,其他程序必須等待直到它完成才能執行。這種方式效率低下且浪費計算資源。

          為了提高計算機系統的資源利用率,研究人員開始探索如何實現并發執行多個程序的方法。最終,進程概念被提出,它可以讓操作系統分配資源和控制多個任務的執行。

          進程的概念最早由斯圖爾特·F·博伊斯(Stuart F.Boyes)在1960年代初在他的博士論文中提出。隨著計算機技術的不斷發展,進程的概念得到了廣泛的應用,成為了計算機操作系統中重要的概念之一。

          什么是進程?

          進程是指正在運行中的程序或應用程序的實例。一個進程由計算機系統分配給它的一定的系統資源和處理這些資源的線程組成,并且可以與其他進程進行通信和協作完成任務

          從操作系統的角度來看,進程是資源分配的基本單位。

          虛擬

          利用進程機制,所有的現代操作系統都支持在同一個時間來完成多個任務。盡管某個時刻,真實的CPU只能運行一個進程,但是從進程自己的角度來看,它會認為自己在獨享CPU(即虛擬CPU),而從用戶的角度來看多個進程在一段時間內是同時執行的,即并發執行。在實際的實現中,操作系統會使用調度器來分配CPU資源。調度器會根據策略和優先級來給各個進程一定的時間來占用CPU,進程占用CPU時間的基本單位稱為時間片,當進程不應該使用CPU資源時,調度器會搶占CPU的控制權,然后快速地切換CPU的使用進程。這種切換對于用戶程序的設計毫無影響,可以認為是透明的。由于切換進程消耗的時間和每個進程實際執行的時間片是在非常小的,以至于用戶無法分辨,所以在用戶看起來,多個進程是在同時運行的

          優先級

          調度器是操作系統中的一個重要組成部分,它負責管理計算機系統中多個進程(或線程)之間的調度和執行。在多任務操作系統中,由于 CPU 只能同時執行一個進程,因此需要通過調度器來協調多個進程的執行。調度器根據一定的算法,分配給每個進程一段時間片,這樣每個進程可以交替運行,并實現多任務處理。

          調度器可以采用多種算法來進行進程調度,包括先來先服務(FCFS)、最短作業優先(SJF)、輪轉調度(Round Robin)等等。每種算法都有特定的優缺點,適用于不同的場景和應用需求。

          調度器還需要考慮進程的優先級、并發訪問、死鎖避免等問題。為了確保各個進程能夠公平地獲得 CPU 時間,調度器會根據不同的策略動態地調整進程的優先級和時間片大小,從而實現高效、公平、穩定的進程調度和管理。

          進程的管理

          Linux 內核使用進程描述符(Process Descriptor,簡稱 task_struct)來管理進程信息。每個進程都有一個唯一的進程 ID(PID),進程描述符中包含了該進程所需的各種信息,包括:

          • 進程狀態:進程可以處于運行、等待、停止或僵尸等不同的狀態,內核會根據進程的狀態動態調度和管理進程。

          • 進程優先級:內核根據進程的優先級來決定給予進程多少 CPU 時間片,以及何時搶占其他進程。

          • 進程資源:進程需要使用系統資源,如 CPU 時間、內存空間、文件句柄、I/O 設備等,內核會為每個進程分配一定的資源,并對其進行統一管理。

          • 父子關系:在 Linux 中,每個進程都有一個父進程,同時也可以創建子進程。進程描述符中記錄了進程之間的關系,以及進程創建和銷毀的時間戳等信息。

          • 進程上下文:進程上下文包括用戶空間和內核空間,進程需要通過系統調用來切換上下文,從而完成與其他進程的交互和通信。

          除此之外,進程描述符還包含了其他很多細節信息,如進程信號量、進程地址空間、進程間通信機制等等。這些信息都是 Linux 內核管理和調度進程的重要依據,保證了系統能夠高效、穩定地運行。

          進程標識符

          為了方便普通用戶定位每個進程,操作系統為每個進程分配了一個唯一的正整數標識符,稱為進程ID。在Linux中,進程之間存在著親緣關系,如果一個進程在執行過程中啟動了另外一個進程,那么啟動者就是父進程,被啟動者就是子進程。

          在Linux啟動時,如果所有的硬件已經配置好的情況下,進程0會被bootloader程序啟動起來,它會配置實時時鐘,啟動init進程(進程1)和頁面守護進程(進程2)

          父子進程

          #include <func.h> int main(){ printf("getpid = %d, getppid = %d\n", getpid(), getppid()); }

          進程的用戶ID和組ID

          #include <func.h> int main(){ uid_t uid; gid_t gid; uid = getuid(); gid = getgid(); printf("uid = %d, gid = %d\n",uid,gid); }

          有效用戶ID和有效組ID通過函數 geteuid() 和getegid() 獲得。

          #include <func.h> int main(){ uid_t euid; gid_t egid; uid = geteuid(); gid = getegid(); printf("euid = %d, egid = %d\n",euid,egid); }

          進程狀態是指操作系統中一個進程正在使用的資源和當前進程在執行過程中的狀態。在操作系統中,通常有以下幾種進程狀態:

        31. 運行狀態(Running):表示進程正在CPU上執行。
        32. 就緒狀態(Ready):表示進程已經分配到了所需的資源,等待CPU的調度執行。
        33. 阻塞狀態(Blocked):表示進程因為某些原因而暫時無法執行,例如等待輸入/輸出操作完成、等待某個信號等。
        34. 創建狀態(New):表示操作系統已經創建了進程控制塊但還沒有分配資源。
        35. 終止狀態(Terminated):表示進程已經運行結束并釋放了所有資源。
        36. 這些狀態可以根據不同的操作系統和實現方式略有不同,但以上五種狀態是較為常見的狀態類型。

          進程狀態圖

          $ps -elf #找到第二列,也就是列首為S的一列 #R 運行中 #S 睡眠狀態,可以被喚醒 #D 不可喚醒的睡眠狀態,通常是在執行IO操作 #T 停止狀態,可能是被暫?;蛘呤潜桓? #Z 僵尸狀態,進程已經終止,但是無法回收資源

          進程的構成

          虛擬內存

          在進程本身的視角中,它除了會認為CPU是獨占的以外,它還會以為自己是內存空間的獨占者,這種從進程視角看到的內存空間被稱為虛擬內存空間。當操作系統中有多個進程同時運行時,為了避免真實的物理內存訪問在不同進程之間發生沖突,操作系統需要提供一種機制在虛擬內存和真實的物理內存之間建立映射。

          進程地址空間

          內核態和用戶態

          用戶態和內核態是操作系統中的兩個不同的運行級別,用于區分進程執行時所擁有的權限和能夠訪問的資源。

          用戶態(User Mode)是指進程在正常情況下的運行狀態,此時進程只能訪問自己的私有地址空間和一些受限制的系統資源,例如文件、網絡等,但不能直接訪問硬件設備。在用戶態下,進程需要通過系統調用(system call)向操作系統請求更高權限的資源或服務。

          內核態(Kernel Mode)是指進程在獲得了操作系統授予的更高權限之后的運行狀態,此時進程可以訪問所有內存空間、硬件設備和系統資源,同時也具有更高的響應速度和處理能力。在內核態下,進程可以直接調用操作系統提供的各種服務,而不需要經過系統調用來進行間接訪問。

          操作系統將進程的運行狀態分為用戶態和內核態,是為了保證系統的穩定性、安全性和效率。在大多數情況下,操作系統會盡可能地將進程保持在用戶態,只有在必要的時候才會切換到內核態,并且盡快返回用戶

          ps -elf //unix 風格ps aux //bsd風格

          top命令
          top命令是一種常用的系統性能監控工具,可以顯示當前系統中進程的運行狀態、資源占用情況和系統負載等信息。

          在終端中輸入top命令后,會打開一個實時的進程監控界面,顯示系統中所有進程的相關信息,包括進程ID、CPU占用率、內存占用率、虛擬內存使用情況、進程優先級等等。同時,也會顯示系統的負載情況,包括CPU、內存和交換空間的使用率等指標。

          top命令還提供了一些交互式功能,例如可以按照某個特定的指標(如CPU或內存占用率)對進程進行排序,或者查看某個具體進程的詳細信息。此外,top命令還支持一些快捷鍵,例如H鍵可顯示線程視圖,M鍵可按內存使用量排序進程等。

          總之,top命令是一款非常實用的系統監控工具,可以幫助管理員及時發現系統異常、診斷問題并優化系統性能。

          linux的優先級

          在Linux系統中,進程的優先級是通過一個稱為“nice值”的整數來表示的。nice值越小,表示進程的優先級越高,反之則越低。

          正常情況下,nice值的范圍為-20到+19之間,其中-20表示最高優先級,+19表示最低優先級。通常情況下,大多數進程的nice值都是0,表示默認優先級。

          除了nice值以外,Linux還提供了另外一個優先級概念,即實時優先級(real-time priority)。實時優先級通常用于對需要及時響應的進程進行特殊處理,例如音頻、視頻播放和實時控制等應用。

          Linux系統中實時優先級的取值范圍為0到99之間,數字越小表示優先級越高。不過,實時優先級只能由特權用戶(例如root用戶)進行設置,普通用戶無法直接設置實時優先級。

          總之,Linux系統中的優先級概念非常重要,可以幫助管理員合理分配系統資源,提高系統穩定性和性能。

          renice
          renice命令是一個用于調整進程優先級nice值的工具,可以改變已經運行的進程或者指定新創建的進程的優先級。

          在Linux系統中,renice命令可以通過以下格式來使用:

          renice priority [-p] pid [...]

          其中,priority表示要設置的新的nice值,pid則表示要調整優先級的進程ID。如果省略-p參數,則表示對當前shell中所有進程進行調整。如果同時指定多個pid,則對這些進程同時進行優先級調整。

          舉個例子,假設我們希望將pid為1234的進程的nice值調整為10,可以使用如下命令:

          renice 10 -p 1234

          執行該命令后,操作系統會重新分配該進程的資源,提高它的運行優先級。

          需要注意的是,renice命令只能降低進程優先級(即增加nice值),不能提高進程優先級。此外,只有具有足夠權限的用戶才能使用renice命令對進程進行優先級調整。

          kill命令

          kill命令是在Linux和其他類Unix操作系統上用來終止進程的命令。

          kill命令一般有兩種使用方式:

        37. 使用進程ID終止進程??梢允褂萌缦赂袷降拿?#xff1a;kill PID,其中PID是指要終止的進程的進程ID。

        38. 使用信號終止進程??梢允褂萌缦赂袷降拿?#xff1a;kill -SIGNAME PID,其中SIGNAME是指要發送的信號名稱(例如TERM表示正常結束信號),PID是指要終止的進程的進程ID。

        39. 需要注意的是,通常情況下,使用kill命令終止進程會發送SIGTERM信號,這個信號告訴進程應該盡快退出,并進行資源清理工作。如果進程沒有響應SIGTERM信號,可以考慮使用SIGKILL信號強制終止進程,這個信號會直接殺死進程并釋放其占用的資源。

          除了kill命令以外,Linux還提供了其他一些用于管理進程的工具,例如pkill、pgrep等命令,它們可以根據進程名或者其他屬性來查找和終止進程。

          kill命令常用的參數如下:

        40. -SIGNAME:指定發送的信號類型,其中SIGNAME可以是信號名稱或者對應的數字。例如,kill -9 PID表示發送SIGKILL信號,而kill -TERM PID表示發送SIGTERM信號。
        41. -l:列出所有可用的信號名稱。
        42. -s:與-SIGNAME參數類似,也是用來指定信號類型的,不過-S參數需要緊跟在kill命令后面,而-SIGNAME則需要使用空格隔開。
        43. -p:指定要終止進程的進程ID列表,多個進程ID之間使用空格分隔。
        44. -a:與-p參數一起使用,表示同時終止該進程的子進程。
        45. -u:指定要終止進程的用戶名稱或者用戶ID。
        46. 需要注意的是,如果沒有指定任何信號類型,則默認發送SIGTERM信號。同時,只有擁有足夠權限的用戶才能使用kill命令終止其他進程。

          系統調用

          #include <func.h> int main(){ system("sleep 20"); return 0;

          fork函數

          在Linux系統中,fork()是一個非常重要的系統調用函數,它用于創建一個新進程。當進程調用fork()函數時,操作系統會創建一個與原進程幾乎完全相同的新進程,包括代碼、數據、堆棧、文件描述符等。

          在調用fork()函數后,父進程和子進程都會繼續執行下去。不過,由于操作系統為每個進程分配了獨立的內存空間,因此父進程和子進程之間的數據是互相獨立的,一個進程對數據的修改不會影響到另一個進程。

          在fork()函數返回后,可以通過返回值來判斷當前進程是父進程還是子進程。具體而言,fork()函數會返回兩次。對于父進程,fork()函數返回新創建子進程的進程ID;而對于子進程,fork()函數返回0。因此,程序可以根據返回值來進行不同的處理。

          使用fork()函數可以很方便地實現多進程并發編程。通常情況下,父進程主要負責協調和管理子進程,例如創建子進程、等待子進程結束以及收集子進程的運行結果等;而子進程則負責實際的計算和處理任務。

          #include <stdio.h> #include <unistd.h>int main() {pid_t pid;pid = fork();if (pid < 0) {// 錯誤處理fprintf(stderr, "fork failed\n");return -1;} else if (pid == 0) {// 子進程printf("Hello from child process!\n");} else {// 父進程printf("Hello from parent process!\n");}return 0; }

          在計算機科學中,fork() 是一個創建新進程的系統調用。它是操作系統中進程管理的核心功能之一。

          具體實現原理如下:

        47. 當一個進程調用 fork() 系統調用時,操作系統會為其創建一個新的進程,這個新進程稱為子進程。子進程是父進程的拷貝,包括代碼段、數據段和堆棧等。
        48. 在創建子進程時,操作系統會復制整個父進程的地址空間,包括代碼區、數據區、棧等,但不會復制文件描述符、信號處理器和一些其他的進程特有的屬性。
        49. 子進程與父進程的唯一區別在于它們擁有不同的進程 ID(PID)和父進程 ID(PPID)。
        50. 在子進程創建完畢后,父進程和子進程開始并行運行。此時它們執行的程序代碼相同,但是它們各自維護著自己的寄存器、程序計數器和內存等資源。
        51. 總之,fork() 的實現原理就是將父進程的地址空間復制一份給子進程,并為子進程分配新的進程 ID 和父進程 ID。通過這種方式,操作系統能夠同時運行多個獨立的進程,從而提高了計算機的利用率。

          #include <stdio.h> #include <unistd.h>int main() {pid_t pid = fork();int i = 0;if (pid == 0) {puts("child");printf("child i = %d, &i = %p\n", i, &i);++i;printf("child i = %d, &i = %p\n", i, &i);} else {puts("parent");printf("parent i = %d, &i = %p\n", i, &i);sleep(1);printf("parent i = %d, &i = %p\n", i, &i);}return 0; } 、、父子進程的地址相同

          exec函數族

          exec() 函數族是一組用于在進程中執行其他程序的函數,在 Linux 系統中,這個函數族包括以下六個函數:

        52. int execl(const char *path, const char *arg0, ... /* (char *)0 */);
        53. int execv(const char *path, char *const argv[]);
        54. int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
        55. int execve(const char *path, char *const argv[], char *const envp[]);
        56. int execlp(const char *file, const char *arg0, ... /* (char *)0 */);
        57. int execvp(const char *file, char *const argv[]);
        58. 這些函數都可以用于執行一個新的程序文件,每個函數的參數略有不同,但核心作用都是相同的。其中,路徑名參數指定了要執行的程序文件所在的路徑和文件名;命令行參數數組則包含了要傳遞給新程序的參數;環境變量參數數組則包含了要設置的新程序的環境變量。

          當成功調用這些函數時,當前進程的代碼、數據和堆棧都會被新程序所替換,然后開始執行新程序的代碼。因此,使用 exec() 函數族時通常需要先調用 fork() 創建一個子進程,然后在子進程中調用 exec() 執行新的程序,以避免當前進程被替換導致程序異常終止。

          #include <stdio.h> #include <stdlib.h> #include <unistd.h>int main() {pid_t pid;pid = fork();if (pid == -1) {perror("fork error");exit(EXIT_FAILURE);}else if (pid == 0) {// 子進程中執行新程序 hellochar *args[] = {"./hello", NULL};execvp(args[0], args);// 如果 execvp 函數調用成功,那么子進程已經被新程序所取代,下面的代碼不會被執行perror("execvp error");exit(EXIT_FAILURE);}else {// 父進程等待子進程結束wait(NULL);printf("Child process has exited\n");}return 0; }

          進程控制

          孤兒進程

          如果父進程先于子進程退出,則子進程成為孤兒進程,此時將自動被PID為1的進程(即init)收養。
          孤兒進程在系統資源方面不會有任何影響,但它們可能會占用一些系統資源,例如文件描述符、內存等等,如果沒有及時處理,可能會造成資源浪費和系統性能下降。

          通常,我們可以使用信號機制來避免孤兒進程的出現。在父進程中捕獲 SIGCHLD 信號并處理子進程的退出狀態,這樣當子進程退出時,父進程會立即得到通知并對其進行處理。

          以下是一個示例代碼,演示了如何使用信號機制來避免孤兒進程:

          #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h>void sigchld_handler(int signum) {pid_t pid;int status;while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", pid, WEXITSTATUS(status));}else if (WIFSIGNALED(status)) {printf("Child process %d terminated due to signal %d\n", pid, WTERMSIG(status));}} }int main() {pid_t pid;struct sigaction sa;// 綁定信號處理函數sa.sa_handler = sigchld_handler;sa.sa_flags = SA_RESTART;sigemptyset(&sa.sa_mask);if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction");exit(EXIT_FAILURE);}pid = fork();if (pid == -1) {perror("fork error");exit(EXIT_FAILURE);}else if (pid == 0) {// 子進程中執行一段簡單的代碼printf("I am child process with PID %d\n", getpid());sleep(10);printf("Child process is exiting\n");exit(EXIT_SUCCESS);}else {// 父進程等待子進程結束printf("I am parent process with PID %d\n", getpid());while (1) {sleep(1);}}return 0; }

          僵尸進程

          在 Linux 中,當一個進程退出時,它并不會立即從系統中消失,而是留下一個稱為“僵尸進程(Zombie Process)”的狀態,這個狀態只有在父進程回收子進程資源后才會被清除。如果父進程沒有及時回收子進程資源,就會導致僵尸進程一直存在于系統中,并占用系統資源。

          通常情況下,當一個子進程結束時,內核會向其父進程發送一個 SIGCHLD 信號,表示子進程已經退出,而父進程可以通過調用 wait() 或 waitpid() 函數來獲取子進程的退出狀態,并釋放相應的資源。如果父進程不處理該信號,或者忽略該信號,那么子進程就會成為一個僵尸進程。

          以下是一個示例代碼,演示了如何創建一個僵尸進程:

          #include <stdio.h> #include <stdlib.h> #include <unistd.h>int main() {pid_t pid;pid = fork();if (pid == -1) {perror("fork error");exit(EXIT_FAILURE);}else if (pid == 0) {// 子進程中執行一段簡單的代碼printf("I am child process with PID %d\n", getpid());sleep(10);printf("Child process is exiting\n");exit(EXIT_SUCCESS);}else {// 父進程沒有回收子進程資源,導致子進程成為僵尸進程printf("I am parent process with PID %d\n", getpid());sleep(20);printf("Parent process is exiting\n");}return 0; }

          在這個例子中,我們使用 fork() 函數創建了一個子進程,并在該子進程中執行了一段簡單的代碼。在父進程中,我們沒做任何處理就休眠了 20 秒鐘后退出。

          由于父進程并沒有回收子進程資源,因此當子進程結束時,它會成為一個僵尸進程??梢酝ㄟ^執行 ps aux 命令查看系統中的進程狀態,發現名為“<defunct>”的進程就是僵尸進程。

          要避免產生僵尸進程,通常需要及時回收子進程資源??梢栽诟高M程中捕獲 SIGCHLD 信號并調用 wait() 或 waitpid() 函數來等待子進程退出,并釋放其資源。

          wait和waitpid

          wait()和waitpid()` 都是用來等待子進程結束的函數,并且在子進程結束后獲取其終止狀態。它們的返回值都是子進程的 PID。

          wait() 函數的原型如下:

          #include <sys/types.h> #include <sys/wait.h>pid_t wait(int *status);

          該函數會掛起調用進程,直到有一個子進程退出,或者收到一個信號,其中 status 參數用于存儲子進程的退出信息,包括退出狀態碼和資源使用情況等。如果不需要獲取這些信息,可以將 status 設置為 NULL。

          waitpid() 函數的原型如下:

          #include <sys/types.h> #include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);

          該函數與 wait() 類似,但可以指定要等待的子進程。pid 參數為要等待的子進程的 PID,如果設置為 -1,則表示等待任何一個子進程。

          options 參數可以用來指定一些附加選項,例如:

          • WNOHANG:非阻塞模式,如果沒有子進程退出,則立即返回 0。
          • WUNTRACED:也等待被暫停的子進程,但不包括已經停止執行的子進程。
          • WCONTINUED:等待之前被暫停的子進程繼續執行。

          waitpid() 函數還可以通過設置 __WALL 標志來等待所有子進程,包括被停止和被恢復執行的子進程。

          需要注意的是,在使用 wait() 或 waitpid() 函數時,必須確保調用它們的進程是要等待的子進程的父進程。否則可能會導致獲取到錯誤的子進程信息或者阻塞當前進程。

          wait()函數是用來等待子進程結束并獲取子進程的退出狀態。如果在調用wait()時沒有傳入參數,則它會等待任何一個子進程結束,并返回該子進程的PID和退出狀態信息。如果希望等待特定的子進程,可以將該子進程的PID作為wait()函數的參數傳入。

          #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h>int main() {pid_t pid;pid = fork();if (pid < 0) {perror("fork error");exit(EXIT_FAILURE);} else if (pid == 0) {printf("child process %d is running\n", getpid());sleep(2);printf("child process %d is finished\n", getpid());exit(EXIT_SUCCESS);} else {printf("parent process %d is waiting for child process %d\n", getpid(), pid);int status;pid_t child_pid = wait(&status);if (child_pid <= 0) {perror("wait error");exit(EXIT_FAILURE);}if (WIFEXITED(status)) {printf("child process %d exited with status %d\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("child process %d exited due to signal %d\n", child_pid, WTERMSIG(status));}}return EXIT_SUCCESS; } #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h>int main() {pid_t pid1, pid2;pid1 = fork();if (pid1 < 0) {perror("fork error");exit(EXIT_FAILURE);} else if (pid1 == 0) {printf("child process 1 %d is running\n", getpid());sleep(2);printf("child process 1 %d is finished\n", getpid());exit(EXIT_SUCCESS);}pid2 = fork();if (pid2 < 0) {perror("fork error");exit(EXIT_FAILURE);} else if (pid2 == 0) {printf("child process 2 %d is running\n", getpid());sleep(4);printf("child process 2 %d is finished\n", getpid());exit(EXIT_FAILURE);}printf("parent process %d is waiting for child processes %d and %d\n", getpid(), pid1, pid2);int status;pid_t child_pid;do {child_pid = waitpid(-1, &status, WUNTRACED | WCONTINUED);if (child_pid <= 0) {break;}if (WIFEXITED(status)) {printf("child process %d exited with status %d\n", child_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("child process %d exited due to signal %d\n", child_pid, WTERMSIG(status));} else if (WIFSTOPPED(status)) {printf("child process %d is stopped by signal %d\n", child_pid, WSTOPSIG(status));} else if (WIFCONTINUED(status)) {printf("child process %d is continued\n", child_pid);}} while (!WIFEXITED(status) && !WIFSIGNALED(status));return EXIT_SUCCESS; }

          進程終止

          守護進程

          守護進程(daemon),就是在默默運行在后臺的進程,也稱作守護進程(daemon)是在操作系統后臺運行的一種特殊進程,通常在系統啟動時自動啟動,并持續運行直到系統關閉。守護進程通常不會與用戶直接交互,而是在后臺執行某些特定任務,例如網絡服務、系統監控、日志記錄等。

          守護進程的特點包括:

        59. 不受任何終端控制,無法通過鍵盤輸入來操縱。
        60. 在系統啟動時自動啟動,并持續運行直到系統關閉。
        61. 通常由超級用戶或系統管理員啟動。
        62. 可以執行特定的任務,如網絡服務、系統監控和日志記錄等。
        63. 在Unix/Linux系統中,守護進程通常通過fork()函數創建子進程,然后讓父進程退出,使子進程成為獨立的進程。為了避免守護進程意外退出或死鎖,通常需要編寫相應的代碼進行異常處理和安全性保障。

          進程組

          進程組(process group)是一組相關聯的進程的集合,它們共享同一個進程組ID(PGID)。進程組可以用來協調和控制一組進程的行為。

          在UNIX/Linux系統中,每個進程都有一個唯一的進程ID(PID),而進程組則是由一個或多個進程組成的。系統給每個進程組分配了一個唯一的PGID,每個進程也有一個PGID,通常與其所屬進程組的PGID相同。進程組中的進程可以通過發送信號來相互通信。

          使用setpgid()函數可以將一個進程加入到另一個進程組中,也可以創建新的進程組。常見的進程組管理命令包括:

        64. ps -o pid,ppid,pgid,args:列出當前所有進程及其父進程ID、進程組ID和命令行參數。
        65. kill -<信號名> <進程組ID>:向指定進程組中的所有進程發送信號。
        66. fg :將后臺進程轉移到前臺,并使其成為當前作業。
        67. bg :將暫停的前臺進程轉換為后臺進程。
        68. 進程組的主要作用是方便進程間的通信和協調。例如,在shell中啟動的管道操作就是將若干個進程組合成一個管道進程組,使得這些進程之間可以進行數據傳輸。另外,進程組還可以使用作業控制功能來控制進程的運行狀態,如在后臺運行、暫停和恢復等。

          #include <stdio.h> #include <unistd.h> #include <sys/wait.h>int main() {pid_t pid = fork();if(pid == 0){printf("child, pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(),getpgid(0));exit(0);}else{printf("parent, pid = %d, ppid = %d, pgid = %d\n", getpid(), getppid(),getpgid(0));wait(NULL);exit(0);} } setpgid()函數是用于設置進程組ID(PGID)的系統調用,其原型如下:```c int setpgid(pid_t pid, pid_t pgid);

          參數pid指定要設置進程組ID的目標進程,參數pgid指定將要設置的進程組ID。如果pid和pgid的值都為0,則使用調用進程的PID作為目標進程,并且將調用進程的PID作為新的進程組ID。

          使用setpgid()函數可以將一個進程加入到另一個進程組中,或者創建新的進程組,例如:

          #include <unistd.h> #include <stdio.h>int main() {pid_t pid1 = getpid();pid_t pid2 = fork(); // create a child processif (pid2 == 0) {// child processsetpgid(0, pid1); // join the parent's process groupprintf("Child process: pid=%d, ppid=%d, pgid=%d\n", getpid(), getppid(), getpgrp());} else {// parent processprintf("Parent process: pid=%d, ppid=%d, pgid=%d\n", getpid(), getppid(), getpgrp());wait(NULL);}return 0; }

          在上面的示例中,子進程調用setpgid()函數將自己加入到父進程的進程組中,并打印出進程ID、父進程ID和進程組ID;而父進程則僅打印出自己的進程ID、父進程ID和進程組ID。

          守護進程的創建流程

          守護進程是一種在后臺運行的長期運行的進程,通常被用來提供某種服務或者執行某些特定的任務。下面是一個簡單的守護進程創建流程:

        69. 創建一個子進程,并通過調用setsid()函數使其成為一個新會話的首進程。
        70. 關閉所有文件描述符(stdin、stdout和stderr除外),這是為了避免意外的輸入輸出并且釋放與父進程的連接??梢酝ㄟ^使用sysconf(_SC_OPEN_MAX)獲取最大文件描述符數目,在之后循環關閉。
        71. 將當前工作目錄切換到根目錄,這是因為絕大多數守護進程需要脫離任何掛載點的依賴。
        72. 重設掩碼,以屏蔽任何文件權限問題,以防影響守護進程的正常運行。
        73. 可選地,將標準輸入、輸出和錯誤輸出重定向到/dev/null或者其他日志文件中,這是為了避免不必要的輸出打印,同時保留有意義的錯誤日志記錄。
        74. 守護進程完成初始化工作,開始執行其正常任務邏輯。
        75. 以下是一個簡單的守護進程創建示例代碼:

          #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h>int main(void) {pid_t pid, sid;/* Create new process */pid = fork();if (pid < 0) {exit(EXIT_FAILURE);}/* If parent process - stop */if (pid > 0) {exit(EXIT_SUCCESS);}/* Child process continues *//* Create a new session for the child process */sid = setsid();if (sid < 0) {exit(EXIT_FAILURE);}/* Close all open file descriptors */int maxfd = sysconf(_SC_OPEN_MAX);for (int fd = 0; fd < maxfd; fd++) {close(fd);}/* Change the working directory to root */chdir("/");/* Reset the file mode creation mask */umask(0);/* Redirect standard I/O streams to /dev/null */int null_fd = open("/dev/null", O_RDWR);dup2(null_fd, STDIN_FILENO);dup2(null_fd, STDOUT_FILENO);dup2(null_fd, STDERR_FILENO);close(null_fd);/* Run daemon process */while (1) {/* Do some work */}exit(EXIT_SUCCESS); }

          這個示例代碼中,守護進程創建后,首先通過setsid()函數來創建新的會話,然后關閉所有文件描述符,并將當前目錄切換到根目錄。接下來,重設掩碼,并將stdin、stdout和stderr標準輸入輸出流重定向到/dev/null文件。最后,啟動一個簡單的任務循環,以使守護進程一直運行。

          未完待續

          上一篇:PT站點注冊掃盲
          下一篇:firefox下的插件

          網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...

          在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...

          在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...

          稻香村月餅為什么那么便宜?稻香村是老糕點品牌,走的是親民路線。月餅的便宜往往與原料價格、包裝和當地價格掛鉤,還需要報當地物價部門審批。同時,經過成本核算,定價上肯定是有利潤空間的。除去原材料、人工、場地費、運輸費、倉儲等費用,稻香村走的是薄利多銷的模式,但質量有保障。稻香村月餅為什么那么便宜?月餅的便宜和便宜往往與原料價格、包裝和當地價格掛鉤,也要報當地物價部門審批。主要是通過選材、人工、成本核算...

          紅包插件是什么意思?紅包的意思是,比如你微信里有人發紅包,會提示你搶紅包。蘋果手機越獄后裝什么插件搶紅包?可以下載紅包助手,也可以從同步加速器搶紅包,都是正版授權軟件,兼容性和穩定性高,不會閃退。我一直在從同步加速器下載軟件。蘋果手機不越獄能裝搶紅包插件嗎?蘋果手機不越獄就不能用微信搶紅包軟件。蘋果手機需要越獄才能安裝紅包軟件。以ios8為例。該方法如下:1.首先,手機越獄后,打開cydia軟件源...

          安徽界首市屬于哪個市 界首在安徽哪里?界首在哪里? 界首,安徽省縣級市,由阜陽市管理,位于安徽省西北部,又稱界溝和小上海。南接臨泉縣、阜陽,東接太和縣,西北與河南省沈丘、丹城交界。因南宋著名將軍劉琦失敗而得名??谷諔馉幤陂g,由于交通堵塞,界首沒有受到日軍的侵犯,上海、南京等城市的商人紛紛遷往界首。一度,人口急劇增加,商人聚集,貿易繁榮,因此被譽為小上海。首領歷史悠久,文化豐富。東漢時期,王莽和...

          TOP
          国产初高中生视频在线观看|亚洲一区中文|久久亚洲欧美国产精品|黄色网站入口免费进人
          1. <nobr id="easjo"><address id="easjo"></address></nobr>

              <track id="easjo"><source id="easjo"></source></track>
              1. 
                

              2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
              3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>