成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文搞懂Linux進程:解鎖操作系統的核心秘密

系統 Linux
在Linux 的世界里,進程就像是一個個活躍的小生命,它們在系統的舞臺上各司其職,共同演繹著復雜而精彩的計算交響樂。

在Linux 的世界里,進程就像是一個個活躍的小生命,它們在系統的舞臺上各司其職,共同演繹著復雜而精彩的計算交響樂。簡單來說,進程是程序的一次執行實例。當你在 Linux 系統中運行一個程序,比如用命令行啟動一個 Python 腳本,系統就會創建一個進程來執行這個腳本。程序本身只是存儲在磁盤上的一組靜態指令和數據,而進程則是這些指令和數據在內存中的動態運行過程。進程具有一些關鍵屬性,這些屬性就像是它們的 “身份標簽” 和 “行為準則”。

比如,每個進程都有一個唯一的進程標識符(PID),這就好比每個人的身份證號碼,系統通過 PID 來識別和管理不同的進程。進程還有自己的狀態,常見的狀態包括運行態(R)、睡眠態(S)、停止態(T)和僵尸態(Z)等 。

  • 運行態表示進程正在 CPU 上執行或準備執行;
  • 睡眠態則是進程在等待某個事件的完成,比如等待 I/O 操作結束;
  • 停止態是進程被暫停,通常是因為接收到了特定的信號;

僵尸態比較特殊,當子進程結束運行但父進程沒有回收其資源時,子進程就會進入僵尸態。進程的優先級也很重要,它決定了進程獲取 CPU 時間的先后順序。優先級高的進程會優先被調度執行,就像在醫院里,急診病人會比普通病人優先得到救治一樣。在 Linux 系統中,進程的優先級可以通過 nice 值來調整,nice 值的范圍是 -20 到 19,值越小優先級越高。不過,普通用戶只能在一定范圍內調整自己進程的 nice 值,而 root 用戶則擁有更大的權限。

一、Linux進程概念

從理論角度看,是對正在運行的程序過程的抽象;

從實現角度看,是一種數據結構,目的在于清晰地刻畫動態系統的內在規律,有效管理和調度進入計算機系統主存儲器運行的程序。

1. 什么是進程?

  • 狹義定義:進程就是一段程序的執行過程。
  • 廣義定義:進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
  • 進程有怎么樣的特征?
  • 動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。
  • 并發性:任何進程都可以同其他進程一起并發執行
  • 獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
  • 異步性:由于進程間的相互制約,使進程具有執行的間斷性,即進程按各自獨立的、不可預知的速度向前推 進
  • 結構特征:進程由程序、數據和進程控制塊三部分組成;
  • 多個不同的進程可以包含相同的程序:一個程序在不同的數據集里就構成不同的進程,能得到不同的結果;但是執行過程中,程序不能發生改變。

2. Linux進程結構?

Linux進程結構:可由三部分組成:代碼段、數據段、堆棧段。也就是程序、數據、進程控制塊PCB(Process Control Block)組成。進程控制塊是進程存在的惟一標識,系統通過PCB的存在而感知進程的存在。

系統通過PCB對進程進行管理和調度。PCB包括創建進程、執行程序、退出進程以及改變進程的優先級等。而進程中的PCB用一個名為task_struct的結構體來表示,定義在include/linux/sched.h中,每當創建一新進程時,便在內存中申請一個空的task_struct結構,填入所需信息,同時,指向該結構的指針也被加入到task數組中,所有進程控制塊都存儲在task[]數組中。

進程的三種基本狀態:

  • 就緒狀態:進程已獲得除處理器外的所需資源,等待分配處理器資源;只要分配了處理器進程就可執行。就緒進程可以按多個優先級來劃分隊列。例如,當一個進程由于時間片用完而進入就緒狀態時,排入低優先級隊列;當進程由I/O操作完成而進入就緒狀態時,排入高優先級隊列。
  • 運行狀態:進程占用處理器資源;處于此狀態的進程的數目小于等于處理器的數目。在沒有其他進程可以 執行時(如所有進程都在阻塞狀態),通常會自動執行系統的空閑進程。
  • 阻塞狀態:由于進程等待某種條件(如I/O操作或進程同步),在條件滿足之前無法繼續執行。該事件發生 前即使把處理機分配給該進程,也無法運行。

3. 進程和程序的區別?

  • 程序是指令和數據的有序集合,是一個靜態的概念。而進程是程序在處理機上的一次執行過程,它是一個 動態的概念。
  • 程序可以作為一種軟件資料長期存在,而進程是有一定生命期的。程序是永久的,進程是暫時的。
  • 進程是由進程控制塊、程序段、數據段三部分組成;
  • 進程具有創建其他進程的功能,而程序沒有。
  • 同一程序同時運行于若干個數據集合上,它將屬于若干個不同的進程,也就是說同一程序可以對應多個進 程。
  • 在傳統的操作系統中,程序并不能獨立運行,作為資源分配和獨立運行的基本單元都是進程。

二、進程的查看與監控

在 Linux 系統中,了解進程的運行狀態是系統管理和調試的重要環節。就像一位交通警察需要時刻了解道路上車輛的行駛情況一樣,系統管理員和開發者需要通過各種工具來查看和監控進程。下面我們就來介紹幾個常用的命令。

1. ps命令

ps 命令就像是進程世界的 “普查員”,它可以讓我們查看系統中正在運行的進程的詳細信息。通過不同的選項組合,ps 命令能夠展現出進程的豐富細節。

比如,使用ps -aux命令,它會列出系統中所有用戶的進程信息。這里的a表示顯示所有用戶的進程(包括其他用戶的進程),u是以用戶友好的格式顯示進程信息(包括用戶名、CPU 和內存使用率等詳細信息),x表示顯示沒有控制終端的進程(通常是后臺進程) 。在我的 Linux 服務器上執行這個命令,得到的部分輸出如下:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  16120  3340?        Ss   08:21   0:01 /sbin/init splash
root         2  0.0  0.0      0     0?        S    08:21   0:00 [kthreadd]
root         3  0.0  0.0      0     0?        S    08:21   0:00 [ksoftirqd/0]

在這些輸出信息中,USER表示進程所屬的用戶;PID是進程的唯一標識符;%CPU顯示進程使用的 CPU 百分比;%MEM是進程使用的內存百分比;VSZ代表進程使用的虛擬內存大?。▎挝唬篕B);RSS是進程使用的物理內存大?。▎挝唬篕B);TTY表示進程所連接的終端設備;STAT是進程狀態,常見的如S表示睡眠態,R表示運行態等;START是進程啟動時間;TIME是進程占用 CPU 的累計時間;COMMAND則是啟動進程的命令名稱和參數。

而ps -ef命令同樣用于顯示所有進程的詳細信息,這里的-e表示顯示所有進程,包括其他用戶的進程,-f是以完整格式顯示進程信息 。它的輸出格式與ps -aux略有不同,但包含的核心信息是一致的。例如:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:21?        00:00:01 /sbin/init splash
root         2     0  0 08:21?        00:00:00 [kthreadd]
root         3     2  0 08:21?        00:00:00 [ksoftirqd/0]

其中,UID是進程所有者的用戶 ID,PPID是父進程 ID,C表示 CPU 使用率,STIME是進程啟動時間,TTY是進程關聯的終端設備,TIME是進程占用的 CPU 時間,CMD是啟動進程的命令名稱和參數。通過這些信息,我們可以清晰地了解每個進程的基本情況。

2. top命令

top 命令則像是一個 “實時監控器”,它為我們提供了系統進程的動態實時視圖,讓我們可以實時了解系統中各個進程的資源占用狀況,就像在觀看一場實時的比賽直播一樣,隨時掌握進程的 “戰況”。

當我們在命令行輸入top命令后,會看到一個動態更新的界面,它每隔一段時間(默認 3 秒)就會自動刷新,展示最新的進程狀態。界面的頂部顯示了系統的一些關鍵信息,如系統的運行時間、當前登錄用戶數、系統負載(load average)等 。接下來是進程統計信息,包括總進程數、正在運行的進程數、睡眠的進程數、停止的進程數和僵尸進程數等。再下面是 CPU 使用情況的詳細統計,包括用戶空間占用 CPU 百分比、內核空間占用 CPU 百分比、空閑 CPU 百分比等 。還有內存和交換分區的使用情況統計。

在 top 命令的交互界面中,我們可以通過一些按鍵來進行各種操作。比如,按下P鍵,進程列表會按照 CPU 使用率從高到低進行排序,這樣我們就能快速找到那些 “吃 CPU 大戶” 的進程;按下M鍵,會按照內存使用率排序,幫助我們定位占用內存較多的進程;按下N鍵,會以 PID 排序 。如果我們想要監控特定用戶的進程,可以按下u鍵,然后輸入用戶名,界面就會只顯示該用戶的進程。當我們發現某個進程占用資源過高且不需要它繼續運行時,可以按下k鍵,然后輸入該進程的 PID,再按下回車鍵,就可以終止該進程(默認發送的是 15 信號,即正常終止進程,如果進程沒有響應,可以再次輸入 9,發送強制終止信號) 。按下q鍵則可以退出 top 命令界面。

3. pstree命令

pstree 命令就像是一位 “家族譜繪制師”,它以樹狀結構展示進程之間的關系,讓我們可以一目了然地看到各個進程之間的父子關系,就像查看一個家族的族譜一樣清晰。

當我們執行pstree命令時,它會以init進程(在 systemd 系統中通常是systemd進程)為根節點,展示整個系統的進程樹。例如,在我的系統上執行pstree命令,得到的部分輸出如下:

systemd─┬─ModemManager───2*[{ModemManager}]
        ├─NetworkManager─┬─dhclient
        │                ├─dnsmasq───2*[{dnsmasq}]
        │                └─2*[{NetworkManager}]
        ├─agetty
        ├─atd
        ├─auditd───{auditd}
        ├─chronyd

從這個輸出中,我們可以清晰地看到systemd是很多進程的父進程,比如ModemManager、NetworkManager等,而NetworkManager又有自己的子進程,如dhclient、dnsmasq等 。如果我們想要查看某個特定進程 ID 的進程樹,可以使用pstree -p PID的形式,其中PID是具體的進程 ID。例如,pstree -p 1會以init進程(PID 通常為 1)為根節點,展示其完整的進程樹,并在每個進程名稱旁邊顯示其 PID 。如果我們只想查看某個用戶的所有進程及其關系,可以使用pstree -u username的形式,其中username是具體的用戶名。

三、進程的控制與管理

1. 進程的啟動與終止

在 Linux 系統中,啟動進程是一項常見的操作。最基本的方式就是在命令行直接輸入命令并回車,此時進程會在前臺運行 。例如,輸入ls命令,系統會立即列出當前目錄下的文件和文件夾,在這個過程中,命令行被該進程占用,直到ls命令執行完畢,才會返回命令提示符,在此期間我們無法在同一命令行執行其他命令。

造成進程產生的主要事件有:

  • 系統初始化
  • 執行進程創立程序
  • 用戶請求創立新進程

造成進程消亡的事件:

  • 進程運行完成而退出。
  • 進程因錯誤而自行退出
  • 進程被其他進程所終止
  • 進程因異常而被強行終結

如果我們希望命令在后臺運行,不占用命令行,以便可以同時執行其他操作,可以在命令后面加上&符號 。比如,我們想要運行一個長時間的計算任務,如python long_computation.py &,這樣該 Python 腳本就會在后臺啟動,命令行馬上返回提示符,我們可以繼續執行其他命令 。通過這種方式,多個命令可以并行執行,提高了工作效率。需要注意的是,使用&符號將命令放到后臺運行后,該進程的父進程還是當前終端 shell 的進程。一旦父進程退出,它會發送 hangup 信號給所有子進程,子進程收到 hangup 信號以后也會退出。

對于已經在前臺執行的命令,如果想要將其放到后臺運行,可以先按下Ctrl+Z組合鍵暫停該進程,然后使用bg命令將其放到后臺繼續運行 。例如,我們在前臺執行vim編輯文件時,突然需要執行其他命令,就可以按下Ctrl+Z暫停vim進程,然后輸入bg命令將vim放到后臺,此時可以執行其他命令。若要將后臺進程恢復到前臺運行,可以使用fg命令,加上對應的作業編號,作業編號可以通過jobs命令查看,jobs命令會顯示所有暫停以及后臺執行的命令及其編號。

當我們不再需要某個進程運行時,就需要終止它。在 Linux 中,常用kill命令和killall命令來終止進程 。kill命令用于終止指定進程 ID(PID)的進程。首先,我們需要通過ps等命令獲取要終止進程的 PID,例如ps -aux | grep process_name,然后使用kill PID來終止該進程。如果進程比較頑固,無法通過正常方式終止,可以使用kill -9 PID來強制終止,-9表示發送 SIGKILL 信號,這是一種強制終止進程的方式,進程無法忽略該信號,但可能會導致數據丟失等問題,所以要謹慎使用 。比如,當一個程序出現死鎖或者無響應時,我們可以使用kill -9來結束它。

killall命令則是根據進程名稱來終止所有匹配該名稱的進程 。它的使用相對簡單,不需要先查找進程 ID,直接使用killall process_name即可。例如,要終止所有的nginx進程,只需輸入killall nginx 。不過,使用killall命令時要特別小心,因為它會終止所有匹配名稱的進程,如果名稱不唯一,可能會誤殺其他有用的進程。

2. 進程優先級調整

進程優先級決定了進程在競爭系統資源(如 CPU 時間)時的先后順序。在 Linux 系統中,進程優先級的范圍是 -20 到 19,數值越小優先級越高 。例如,一個優先級為 -10 的進程會比優先級為 5 的進程更優先獲得 CPU 時間。

我們可以通過ps -l命令查看進程的優先級相關信息,其中PRI字段表示進程的實際優先級,NI字段表示進程的 nice 值 。nice 值不是進程的優先級本身,而是用于調整優先級的修正數據,進程的實際優先級(PRI)會根據 nice 值進行調整,計算公式為PRI = PRI(old) + nice,通常初始的 PRI 值為 80 。比如,一個進程的 nice 值為 0,那么它的實際優先級 PRI 就是 80;如果將其 nice 值調整為 5,那么新的實際優先級 PRI 就變為 85,優先級相對降低。

如果想要改變進程的優先級,可以使用nice命令和renice命令 。nice命令主要用于在啟動進程時設置其優先級。例如,我們要以較高的優先級啟動一個top命令來實時監控系統資源,可以使用nice -n -10 top,這里-n選項后面的 -10 表示設置的 nice 值,數值越小優先級越高 。這樣啟動的top進程就會比普通啟動的top進程更優先獲得 CPU 資源,能更及時地反映系統狀態。

renice命令則用于修改已經在運行的進程的優先級 。假設我們有一個正在運行的進程,其 PID 為 12345,現在想要將它的優先級降低,可以使用renice -n 5 12345,這會將該進程的 nice 值設置為 5,從而調整其實際優先級 。在實際應用中,比如當我們發現某個后臺進程占用過多 CPU 資源,影響到其他更重要的進程運行時,就可以使用renice命令降低它的優先級,確保系統資源能合理分配給各個進程。

3. 進程間通信

在 Linux 系統中,多個進程常常需要相互協作、交換信息,這就需要進程間通信(IPC,Inter-Process Communication)機制 。比如,一個網絡服務器進程可能需要與多個客戶端進程進行數據交互,一個圖形界面程序可能需要與后臺的數據處理進程通信來獲取和展示數據。下面介紹幾種常見的進程間通信方式。

(1) 管道(Pipe)

管道是一種半雙工的通信方式,數據只能單向流動,而且通常只能在具有親緣關系(如父子進程)的進程間使用 。它在內核中維護了一塊內存作為緩沖區,有讀端和寫端 。我們可以使用pipe函數創建無名管道,父進程創建管道后通過fork子進程,子進程會繼承父進程的管道文件描述符,從而實現父子進程間的通信 。例如,父進程向管道寫端寫入數據,子進程從管道讀端讀取數據。

管道的讀寫遵循一定規則,當管道的寫端不存在時,讀操作會認為已經讀到數據末尾,返回的讀出字節數為 0;當管道的寫端存在時,如果請求讀取的字節數目大于管道緩沖區的大小(PIPE_BUF),則返回管道中現有的數據字節數,如果請求的字節數目不大于 PIPE_BUF,則返回管道中現有數據字節數(當管道中數據量小于請求的數據量時)或者返回請求的字節數(當管道中數據量不小于請求的數據量時) 。向管道中寫入數據時,如果管道緩沖區已滿,寫操作將一直阻塞,直到有數據被讀取,緩沖區有空閑空間 。例如:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(void) {
    char buf[32] = {0};
    pid_t pid;
    int fd[2];

    // 創建無名管道
    if (pipe(fd) == -1) {
        perror("pipe");
        return 1;
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid > 0) {
        // 父進程
        close(fd[0]); // 關閉讀端
        write(fd[1], "hello", 5); // 向寫端寫入數據
        close(fd[1]); // 關閉寫端
        wait(NULL); // 等待子進程結束
    } else {
        // 子進程
        close(fd[1]); // 關閉寫端
        read(fd[0], buf, 32); // 從讀端讀取數據
        printf("buf is %s\n", buf);
        close(fd[0]); // 關閉讀端
    }

    return 0;
}

在這個例子中,父進程創建管道后,通過fork創建子進程,然后父進程關閉讀端,向寫端寫入 “hello”,子進程關閉寫端,從讀端讀取數據并打印。

(2) 命名管道(Named Pipe,FIFO)

命名管道也是半雙工的通信方式,但它允許無親緣關系的進程間通信 。它的實質是內核維護的一塊內存,表現為一個有名字的文件 。我們可以使用mkfifo函數創建命名管道,不同進程通過打開同一個命名管道文件來進行通信 。比如,一個進程以寫模式打開命名管道,另一個進程以讀模式打開,就可以實現數據的傳輸 。例如,有兩個進程writer.c和reader.c,writer.c負責向命名管道寫入數據,reader.c負責從命名管道讀取數據:

// writer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    int fd;
    char cont_w[] = "hello sundy";

    // 創建命名管道
    if (mkfifo("myfifo", 0666) == -1) {
        perror("mkfifo");
        return 1;
    }

    // 以寫模式打開命名管道
    fd = open("myfifo", O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    write(fd, cont_w, sizeof(cont_w));
    close(fd);

    return 0;
}
// reader.c
/*
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    int fd;
    char buf[32] = {0};

    // 以讀模式打開命名管道
    fd = open("myfifo", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    read(fd, buf, sizeof(buf));
    printf("buf is %s\n", buf);
    close(fd);

    return 0;
} */

在這個例子中,writer.c創建命名管道 “myfifo” 并寫入數據,reader.c打開該命名管道讀取數據并打印。

(3) 共享內存(Shared Memory)

共享內存是一種高效的進程間通信方式,它允許多個進程直接訪問同一塊內存區域 。共享內存的實現是通過將內存段映射到共享它的進程的地址空間,這樣進程間的數據傳送不再需要通過內核進行多次數據復制,大大提高了通信效率 。使用共享內存時,通常需要配合信號量等機制來實現進程間的同步和互斥,防止多個進程同時訪問共享內存時出現數據沖突 。例如,一個進程創建共享內存段并寫入數據,其他進程可以通過映射該共享內存段來讀取數據 。下面是一個簡單的示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shm, *s;

    // 生成一個唯一的鍵值
    key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 創建共享內存段
    shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    // 將共享內存段映射到進程地址空間
    shm = (char *)shmat(shmid, NULL, 0);
    if (shm == (char *)-1) {
        perror("shmat");
        return 1;
    }

    s = shm;
    // 向共享內存寫入數據
    for (int i = 0; i < 10; i++) {
        sprintf(s, "This is %d\n", i);
        s += strlen(s);
    }

    // 等待其他進程讀取數據
    while (*shm != '*') {
        sleep(1);
    }

    // 取消映射
    if (shmdt(shm) == -1) {
        perror("shmdt");
        return 1;
    }

    // 刪除共享內存段
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl");
        return 1;
    }

    return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define SHM_SIZE 1024

int main() {
    key_t key;
    int shmid;
    char *shm, *s;

    // 生成相同的鍵值
    key = ftok(".", 'a');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 獲取共享內存段
    shmid = shmget(key, SHM_SIZE, 0666);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    // 將共享內存段映射到進程地址空間
    shm = (char *)shmat(shmid, NULL, 0);
    if (shm == (char *)-1) {
        perror("shmat");
        return 1;
    }

    // 從共享內存讀取數據
    s = shm;
    while (*s != '\0') {
        printf("%s", s);
        s += strlen(s);
    }

    // 標記數據已讀取
    *shm = '*';

    // 取消映射
    if (shmdt(shm) == -1) {
        perror("shmdt");
        return 1;
    }

    return 0;
}

在這個示例中,第一個進程創建共享內存并寫入數據,然后等待第二個進程讀取數據并標記,第二個進程獲取共享內存并讀取數據,讀取完成后標記數據已讀取。

(4) 消息隊列(Message Queue)

消息隊列是由消息的鏈表組成,存放在內核中并由消息隊列標識符標識 。它克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點 。進程可以向消息隊列發送消息,也可以從消息隊列接收消息 。消息隊列中的消息具有類型,接收進程可以根據消息類型有選擇地接收消息 。例如,在一個多進程協作的系統中,不同的進程可以根據自己的需求向消息隊列發送不同類型的消息,其他進程可以根據自身需要從消息隊列中獲取特定類型的消息進行處理 。下面是一個簡單的消息隊列使用示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

#define MSG_SIZE 128

// 消息結構
typedef struct msgbuf {
    long mtype;
    char mtext[MSG_SIZE];
} msgbuf;

int main() {
    key_t key;
    int msgid;
    msgbuf msg;

    // 生成一個唯一的鍵值
    key = ftok(".", 'b');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 創建消息隊列
    msgid = msgget(key, IPC_CREAT | 0666);
    if (msgid == -1) {
        perror("msgget");
        return 1;
    }

    // 填充消息
    msg.mtype = 1;
    strcpy(msg.mtext, "Hello, message queue!");

    // 發送消息
    if (msgsnd(msgid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
        perror("msgsnd");
        return 1;
    }

    printf("Message sent.\n");

    return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

#define MSG_SIZE 128

// 消息結構
typedef struct msgbuf {
    long mtype;
    char mtext[MSG_SIZE];
} msgbuf;

int main() {
    key_t key;
    int msgid;
    msgbuf msg;

    // 生成相同的鍵值
    key = ftok(".", 'b');
    if (key == -1) {
        perror("ftok");
        return 1;
    }

    // 獲取消息隊列
    msgid = msgget(key, 0666);
    if (msgid == -1) {
        perror("msgget");
        return 1;
    }

    // 接收消息
    if (msgrcv(msgid, &msg, MSG_SIZE, 1, 0) == -1) {
        perror("msgrcv");
        return 1;
    }

    printf("Received message: %s\n", msg.mtext);

    return 0;
}

在這個示例中,第一個進程創建消息隊列并發送一條類型

四、特殊進程剖析

1. 守護進程

守護進程,也被稱為精靈進程,是 Linux 系統中一類特殊的進程,它們如同默默守護系統的 “幕后英雄”,在后臺持續運行,執行著各種重要的系統任務 。與普通進程不同,守護進程脫離于終端運行,這意味著它們不受終端的控制,也不會在終端上顯示輸出信息,即使終端關閉,守護進程依然能夠繼續運行 。就像一位忠誠的守衛,無論周圍環境如何變化,始終堅守崗位。

守護進程具有一些顯著的特點。首先,它們在后臺長期運行,通常在系統啟動時就被自動啟動,并且一直持續運行到系統關閉 。比如,系統日志守護進程syslogd,它負責收集和記錄系統中的各種日志信息,從系統啟動的那一刻起,它就開始默默地工作,記錄著系統運行過程中的點點滴滴,為系統管理員提供了重要的故障排查和系統監控依據 。其次,守護進程沒有控制終端,這使得它們能夠獨立運行,不依賴于用戶的交互操作 。

例如,網絡守護進程sshd,它允許用戶通過 SSH 協議遠程登錄到系統,即使沒有用戶在本地終端進行操作,sshd依然在后臺監聽網絡端口,等待用戶的連接請求,保障了遠程管理的便捷性 。另外,守護進程通常以系統權限運行,這賦予了它們訪問系統關鍵資源和執行重要任務的能力 。比如,cron守護進程,它負責執行周期性的任務,如定時備份文件、更新系統軟件等,需要具備足夠的權限來操作相關的文件和目錄 。

在 Linux 系統中,有許多常見的守護進程。除了前面提到的syslogd、sshd和cron,還有httpd(Apache Web 服務器守護進程),它負責處理 Web 服務器的請求,使得我們能夠在瀏覽器中訪問網站;mysqld(MySQL 數據庫服務器守護進程),它管理著 MySQL 數據庫,為各種應用程序提供數據存儲和檢索服務 。這些守護進程在系統中各司其職,共同維持著系統的穩定運行 。

對于守護進程的管理,我們可以使用不同的工具和方法 。在基于 Systemd 的系統中,使用systemctl命令來管理守護進程非常方便 。比如,要啟動httpd守護進程,可以使用systemctl start httpd命令;要停止它,使用systemctl stop httpd命令;要查看其狀態,使用systemctl status httpd命令 。如果希望httpd守護進程在系統啟動時自動啟動,可以使用systemctl enable httpd命令;如果不想讓它自動啟動,則使用systemctl disable httpd命令 。另外,對于一些簡單的守護進程,我們也可以使用nohup命令來啟動,它可以讓進程在后臺運行,并且忽略掛斷信號 。

例如,nohup my_daemon &可以將my_daemon這個守護進程在后臺啟動,輸出信息會被重定向到nohup.out文件中 。還有supervisor,它是一個功能強大的進程管理工具,可以方便地管理和監控守護進程 。通過配置supervisor的配置文件,我們可以定義守護進程的啟動命令、自動重啟策略、日志輸出等 。比如,在/etc/supervisor/conf.d/目錄下創建一個守護進程的配置文件,然后使用supervisorctl命令來啟動、停止、重啟守護進程,還可以查看其狀態和日志 。

2. 僵尸進程與孤兒進程

在 Linux 進程的世界里,僵尸進程和孤兒進程是兩種比較特殊的進程狀態,它們有著獨特的產生原因和特點。

僵尸進程是指子進程已經終止運行,但父進程沒有調用wait或waitpid函數來獲取子進程的退出狀態信息,此時子進程就會變成僵尸進程 。簡單來說,就像孩子已經長大離開家(子進程結束),但家長(父進程)卻沒有去了解孩子的情況,孩子就一直處于一種 “懸而未決” 的狀態 。

從系統的角度看,僵尸進程雖然已經不再占用 CPU 等運行資源,但它的進程描述符仍然保留在系統中,占用著進程表的一個位置 。這就好比一個已經畢業的學生,雖然不再在學校上課(不占用運行資源),但學校的學籍系統里還保留著他的學籍信息(占用進程表位置) 。如果系統中存在大量的僵尸進程,會導致進程號資源被大量占用,因為系統所能使用的進程號是有限的,當進程號耗盡時,系統將無法創建新的進程,這會對系統的正常運行造成嚴重影響 。

例如,我們來看下面這段 C 代碼:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        // 子進程
        printf("Child process, PID: %d\n", getpid());
        exit(0);
    } else {
        // 父進程
        printf("Parent process, PID: %d\n", getpid());
        while (1) {
            sleep(1);
        }
    }
    return 0;
}

在這段代碼中,父進程創建子進程后,子進程很快退出,但父進程沒有調用wait或waitpid來處理子進程的退出,此時子進程就會變成僵尸進程 。我們可以通過ps -aux命令查看進程狀態,會發現處于僵尸狀態的子進程,其狀態字段顯示為Z 。

為了避免僵尸進程的產生,我們可以采取一些措施 。一種方法是在父進程中調用wait或waitpid函數來等待子進程的結束,并獲取其退出狀態信息 。wait函數會使父進程阻塞,直到有子進程結束;waitpid函數則更加靈活,可以指定等待特定的子進程,并且可以設置非阻塞模式 。例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        // 子進程
        printf("Child process, PID: %d\n", getpid());
        exit(0);
    } else {
        // 父進程
        printf("Parent process, PID: %d\n", getpid());
        int status;
        waitpid(pid, &status, 0);
        printf("Child process has exited\n");
    }
    return 0;
}

在這個改進的代碼中,父進程調用了waitpid函數等待子進程結束,這樣就不會產生僵尸進程 。

另一種避免僵尸進程的方法是利用信號機制 。當子進程結束時,會向父進程發送SIGCHLD信號,我們可以在父進程中注冊SIGCHLD信號的處理函數,在處理函數中調用wait或waitpid來處理子進程的退出 。例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sigchld_handler(int signo) {
    pid_t pid;
    int status;
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        printf("Child process %d has exited\n", pid);
    }
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        // 子進程
        printf("Child process, PID: %d\n", getpid());
        exit(0);
    } else {
        // 父進程
        printf("Parent process, PID: %d\n", getpid());
        while (1) {
            sleep(1);
        }
    }
    return 0;
}

在這段代碼中,通過sigaction函數注冊了SIGCHLD信號的處理函數sigchld_handler,當子進程結束時,會調用該處理函數來處理子進程的退出,從而避免了僵尸進程的產生 。

孤兒進程則是指父進程在子進程之前退出,導致子進程失去了父進程的管理,此時子進程就成為了孤兒進程 。這就像孩子還在成長(子進程還在運行),但家長卻提前離開了(父進程先退出) 。在 Linux 系統中,孤兒進程會被init進程(在 systemd 系統中通常是systemd進程)收養,init進程會負責回收孤兒進程的資源 。例如,下面這段代碼:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        // 子進程
        printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid());
        sleep(5);
        printf("Child process, PID: %d, PPID: %d\n", getpid(), getppid());
    } else {
        // 父進程
        printf("Parent process, PID: %d\n", getpid());
        exit(0);
    }
    return 0;
}

在這個例子中,父進程創建子進程后立即退出,子進程在睡眠 5 秒前后查看自己的父進程 ID,會發現父進程 ID 變成了init進程的 ID(通常為 1),說明子進程已經被init進程收養,成為了孤兒進程 。

孤兒進程本身對系統并沒有太大的危害,因為init進程會妥善處理它們的資源回收 。但在某些情況下,我們可能需要對孤兒進程進行特殊的處理或監控 。比如,如果我們希望在孤兒進程中執行一些特定的清理操作,可以在子進程中檢測父進程是否已經退出,如果發現父進程退出,就執行相應的清理代碼 。例如:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        // 子進程
        sleep(1);
        if (getppid() == 1) {
            printf("I am an orphan process. Performing clean-up...\n");
            // 執行清理操作
        }
        while (1) {
            sleep(1);
        }
    } else {
        // 父進程
        printf("Parent process, PID: %d\n", getpid());
        exit(0);
    }
    return 0;
}

在這段代碼中,子進程在睡眠 1 秒后檢查自己的父進程 ID,如果發現父進程 ID 為 1,說明自己成為了孤兒進程,然后執行相應的清理操作 。

五、Linux進程實戰演練

1. 案例一:優化系統性能

在日常的 Linux 系統運維中,我們常常會遇到系統性能下降的情況,這時候就需要通過各種手段來找出問題所在并進行優化。其中,找出占用資源高的進程并進行相應處理是一個重要的優化步驟。

假設我們在一臺運行著多個服務的 Linux 服務器上,突然發現系統響應變得遲緩,用戶反饋一些應用程序加載緩慢。這時候,我們首先想到的就是使用top命令來查看系統中各個進程的資源占用情況。在終端中輸入top命令后,我們會看到一個動態更新的界面,其中%CPU和%MEM列分別顯示了每個進程占用 CPU 和內存的百分比。通過觀察這兩列數據,我們發現一個名為big_computation.py的 Python 進程占用了高達 80% 的 CPU 資源,同時占用了大量的內存 。

top - 10:15:23 up 2 days,  1:23,  2 users,  load average: 2.50, 2.00, 1.50
Tasks: 200 total,   2 running, 198 sleeping,   0 stopped,   0 zombie
%Cpu(s): 80.0 us, 10.0 sy,  0.0 ni,  5.0 id,  5.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7914.2 total,    123.4 free,   6543.2 used,   1247.6 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   1024.0 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
12345 user1     20   0  512340 204800  12340 R  80.0  2.5   5:10.00 python big_computation.py

這個進程可能是導致系統性能下降的 “罪魁禍首”。如果這個進程是一個臨時的計算任務,且目前并非急需它運行,我們可以考慮終止它以釋放系統資源。通過ps -ef命令進一步確認該進程的詳細信息,獲取其 PID 為 12345,然后使用kill命令來終止它 。

ps -ef | grep big_computation.py
user1    12345     1  80 10:00 ?        00:05:10 python big_computation.py
kill 12345

執行kill命令后,再次查看top命令的輸出,發現系統的 CPU 使用率和內存占用率都有了明顯的下降,系統響應速度也恢復正常 。

如果這個進程是一個長期運行的服務,我們不能直接終止它,但可以嘗試調整其優先級,讓它少占用一些 CPU 資源,以保證其他更重要的進程能夠正常運行 。比如,使用renice命令將其優先級降低。假設我們希望將其 nice 值調整為 10(nice 值越大優先級越低) 。

renice -n 10 12345

執行上述命令后,再次觀察top命令的輸出,會發現該進程的優先級降低,CPU 使用率也有所下降,系統的整體性能得到了優化 。通過這樣的實戰操作,我們能夠更好地掌握 Linux 系統中進程的監控和優化技巧,確保系統的穩定高效運行 。

2. 案例二:進程間通信實現

進程間通信在 Linux 系統中有著廣泛的應用場景,下面我們通過一個簡單的示例來展示使用管道實現進程間數據傳遞的方法。

假設我們有一個數據生成進程和一個數據處理進程,數據生成進程不斷生成一些隨機數,數據處理進程需要接收這些隨機數并進行平方計算 。我們可以使用管道來實現這兩個進程之間的數據傳遞。

首先,我們創建一個 C 語言程序來實現數據生成進程(producer.c) :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main() {
    int pipe_fd[2];
    if (pipe(pipe_fd) == -1) {
        perror("pipe");
        return 1;
    }

    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子進程,作為數據處理進程
        close(pipe_fd[1]); // 關閉寫端
        int num;
        while (read(pipe_fd[0], &num, sizeof(int)) > 0) {
            int result = num * num;
            printf("Processed result: %d\n", result);
        }
        close(pipe_fd[0]); // 關閉讀端
    } else {
        // 父進程,作為數據生成進程
        close(pipe_fd[0]); // 關閉讀端
        srand(time(NULL));
        for (int i = 0; i < 5; i++) {
            int num = rand() % 100;
            write(pipe_fd[1], &num, sizeof(int));
            printf("Generated number: %d\n", num);
            sleep(1);
        }
        close(pipe_fd[1]); // 關閉寫端
        wait(NULL); // 等待子進程結束
    }

    return 0;
}

在這個程序中,首先創建一個管道,然后通過fork函數創建子進程。父進程作為數據生成進程,關閉管道的讀端,生成 5 個隨機數并通過管道的寫端發送給子進程 。子進程作為數據處理進程,關閉管道的寫端,從管道的讀端讀取數據,對讀取到的數據進行平方計算并打印結果 。

編譯并運行這個程序:

gcc -o producer producer.c
./producer

運行結果如下:

Generated number: 34
Processed result: 1156
Generated number: 78
Processed result: 6084
Generated number: 12
Processed result: 144
Generated number: 91
Processed result: 8281
Generated number: 56
Processed result: 3136

從運行結果可以清晰地看到,數據生成進程不斷生成隨機數并發送給數據處理進程,數據處理進程成功接收并進行了相應的處理 。通過這個簡單的案例,我們展示了使用管道實現進程間通信的基本方法,在實際應用中,可以根據具體需求對這個示例進行擴展和優化,以滿足更復雜的進程間通信場景 。

責任編輯:趙寧寧 來源: 深度Linux
相關推薦

2022-04-12 09:05:30

Linux時鐘

2022-03-28 19:19:45

Linux時間子系統

2020-09-27 08:02:47

操作系統

2020-09-03 06:35:44

Linux權限文件

2020-12-19 16:12:58

操作系統計算機科學

2023-03-27 09:08:11

Linux

2022-03-24 08:51:48

Redis互聯網NoSQL

2024-04-12 12:19:08

語言模型AI

2023-12-15 15:55:24

Linux線程同步

2023-12-21 08:02:21

CPUJava8列表

2025-04-23 00:00:00

2021-07-10 14:32:30

Python導入模塊

2016-12-23 14:08:30

物聯網操作系統開源

2023-09-15 12:00:01

API應用程序接口

2023-09-08 08:20:46

ThreadLoca多線程工具

2021-03-22 10:05:59

netstat命令Linux

2021-01-06 13:52:19

zookeeper開源分布式

2025-04-28 02:22:00

2022-01-06 18:21:00

Hadoop生態系統

2021-05-06 05:38:48

Python文件操作異常模塊
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 美女三区 | 狠狠爱视频 | 精品一区在线 | 日本三级网站在线观看 | 日韩视频福利 | 一级黄色在线 | 国产免费一区二区 | 成年人在线电影 | 日韩高清一区二区 | 国产精品久久 | 一区二区三区视频播放 | 亚洲天堂久久新 | 欧州一区 | 99热这里有精品 | 日本在线网址 | 一区二区三区亚洲 | 99国产精品久久久久老师 | 99久视频 | 午夜爱爱网 | 亚州无限乱码 | www.887色视频免费 | 一区二区免费在线 | 99亚洲精品 | 久久免费高清视频 | 日韩av高清| 天天干夜夜操视频 | 国产欧美一区二区三区另类精品 | 亚洲一区二区三区观看 | 国产精品观看 | 99热在线播放 | 久久久91 | 亚洲欧美日韩高清 | 成人国产a | 欧美精品在线观看 | k8久久久一区二区三区 | 欧美精品在线播放 | 在线免费观看成人 | 亚洲自拍偷拍免费视频 | 国产精品毛片在线 | 国产精品久久国产精品久久 | 日日综合 |