Linux(十五)進程間關係和守護進程

進程組/作業/會話

1.進程組
每個進程除了進程ID之外,還屬於一個進程組。進程組是一個或多個進程的集合。通常,他們與一個作業相關聯,可以接受來自同一終端的各種信號。每個進程組有一個唯一的進程組ID。每個進程組都可以有一個組長進程。組長進程的標識是,其進程組ID等於其進程ID。組長進程可以創建一個進程組,創建該組中的進程,然後終止。只要某個進程組中一個進程存在,則該進程組就存在,這與其組長進程是否終止無關
這裏寫圖片描述
& :表示將進程組放在後臺執行
進程:81017 81018 81019
組長:81017,該組的第一個進程,其ID與進程組ID一樣
ps選項:
a:不僅列當前用戶的進程,也列出其他用戶的進程
x:表示不僅列有控制終端的進程,也列出所有無控制終端的進程
j:表示列出與作業控制相關的信息

2.作業
shell分前後臺來控制的不是進程而是作業,或者進程組。一個前臺作業可以由多個進程組成,一個後臺也可以由多個進程組成,shell可以運行一個前臺和任意多個後臺作業,這稱爲作業控制

作業與進程組的區別:如果作業中的某個進程又創建了子進程,則子進程不屬於作業。

一旦作業運行結束,shell就把自己提到前臺(子進程還在,可是子進程不屬於作業),如果原來的前臺進程還存在(如果這個子進程還沒終止),它就會自動變爲後臺進程組。
我們重新理解一下,在前臺新起作業,shell是無法運行,因爲他被提到了後臺。但是如果前臺進程退出,shell就又被提到了前臺,所以可以繼續接受用戶輸入

下面我們來看一個例子

#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {//child
        while(1)
        {
            printf("child(%d)# i am running!\n",getpid());
            sleep(1);
        }

    }
    else
    {
        int i = 5;
        while(i)
        {
            printf("parent(%d)# i am going to dead...%d\n",getpid(),i--);
            sleep(1);
        }
    }
    return 0;
}

這裏寫圖片描述
我們可以發現,程序跑起來之後,在前臺新起了一個作業,包含父子兩個進程,五秒之內shell是無法接受任何命令的,說明此事的前臺作業不是shell,當五秒後,父進程退出,子進程不屬於作業,此時shell就把自己提到了前臺,我們發現shell是可以處理命令的。
這就是前面說的新起的作業退出了,但是子進程還在,所以就被提到後臺

3.會話
會話是一個或多個進程組的集合。一個會話可以有一個控制終端。這通常是登錄到其上的終端設備(終端登錄情況)或僞終端設備(網絡登錄情況)。建立與控制終端連接的會話首進程稱爲控制進程。一個會話中的幾個進程組可被分爲一個前臺進程組以及一個或多個後臺進程組。所以一個會話中,應該包括控制進程(會話首進程)一個前臺進程組和任意後臺進程組
這裏寫圖片描述
我們可以看到會話id SID:80974
我們通過ps aux | grep -E 80974 | grep -v grep
可以看到會話首進程就是bash,而且三個進程父進程都是bash。

每打開一個終端,就新建了一個會話

作業控制

Session與進程組“Shell可同時運行一個前臺進程和任意多個後臺進程”其實是不全面的,現在我們來研究更復雜的情況,事實上,Shell分前後臺來控制的不是進程,而是作業或者進程組,一個前臺作業可以由多個進城組成,一個後臺作業也可以由多個進程組成,Shell可以同時運行一個前臺作業和任意多個後臺作業,這稱爲作業控制

作業控制有關的信號

將cat放到後臺運行,由於cat需要讀標準輸入,而後臺進程是不能讀標準輸入的,因此內核發SIGTTIN信號給進程,該信號的默認處理動作是使進程停止
jobs命令可以查看當前有哪些作業。fg命令可以將某個作業提至前臺運行,如果該作業的進程組正在後臺運行,則提至前臺運行,如果該作業處於停止狀態,則給進程組的每個進程發SIGCONT信號使它繼續運行。參數%1表示將第一個作業提至前臺運行。cat提到前臺運行後,掛起等待終端輸入,當輸入hello並回車後,cat打印出同樣的一行,然後繼續掛起等待輸入。

守護進程

認識守護進程
守護進程也稱精靈進程(Daemon),是運行在後臺的一種特殊進程,它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事情。守護進程是一種很有用的進程。Linux的大多數服務器就是用守護進程實現的。比如ftp服務器,Web服務器等。同時守護進程完成許多系統任務。比如,作爲規劃進程crond等
Linux系統啓動時會啓動很多系統服務進程,這些系統服務進程沒有控制終端,不能直接和用戶交互。其他進程都是在用戶登錄或運行程序時創建,在運行結束或用戶註銷時終止,但系統服務進程(守護進程)不收用戶登錄註銷影響,他們一直在運行着,這中近程叫做守護進程

我們可以用

ps axj | more

查看系統中的進程
凡是TPGID一欄寫着-1 的都是沒有控制終端的進程,也就是守護進程
在COMMAND一列用【】括起來的名字表示內核線程,這些線程在內核裏創建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常以k開頭的名字,表示Kernel。
守護進程通常採用以d結尾的名字,表示Daemon。

創建守護進程
創建守護進程最關鍵的一步是調用setsid函數創建一個新的Session,併成功爲Session Leader。

#include <unistd.h>
pid_t setsid(void);
該函數調用成功時返回新創建的Session的id(也就是當前進程ID)出錯返回-1

需要注意的是,調用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader很容易,我們先fork再調用setsid就可以了。fork創建的子進程和父進程在同一個進程組中,進程組的Leader必然是進程組的第一個進程,也就是父進程。

成功調用該函數的結果是:
*創建一個新的Session,當前進程成爲Session Leader,當前進程id就是Session的id
*創建一個新的進程組,當前進程成爲進程組的Leader,當前進程id就是進程組的id
如果當前進程原本有一個控制終端,則它失去這個控制終端,成爲一個沒有控制終端的進程。所謂失去控制終端就是原來的終端仍然是打開的,礽貪可以讀寫,但是隻是一個普通的打開文件,而不是控制終端了

下面是具體的代碼實現

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

void mydaemon(void)
{
    int i;
    int fd0;
    pid_t pid;
    struct sigaction sa;
    umask(0);//1.調用umask將文件模式創建屏蔽字設置爲0.
    //2.調用fork,父進程退出(exit)
    //如果該守護進程是作爲一條簡單的shell命令啓動的
    //,那麼父進程終止使得shell認爲該命令已經執行完畢
    //保證子進程不是一個進程組的組長進程
    if((pid = fork()) < 0)
    {
        perror("fork");
    }
    else if(pid > 0)
    {
        exit(0);
    }
    setsid();//3.調用setsid創建一個新會話

    sa.sa_handler = SIG_IGN;//忽略SIGCHLD信號
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if(sigaction(SIGCHLD,&sa,NULL) < 0)
    {
        return;
    }
    //注意!再次fork,終止父進程,保持子進程不是話首進程,從而保證後續不會再和其他終端關聯!
    //這部分不是必須的
    if((pid = fork()) < 0)
    {
        printf("fork error!\n");
        return;
    }
    else if(pid != 0)
    {
        exit(0);
    }
    if(chdir("/") < 0)
    {
        //5.將當前工作目錄更改爲根目錄
        printf("child dir error\n");
        return;
    }
    //關閉不再需要的文件描述符,或者重定向到 /dev/null
    close(0);
    fd0 = open("/dev/null",O_RDWR);
    dup2(fd0,1);
    dup2(fd0,2);
}
int main()
{
    mydaemon();
    while(1)
    {
        sleep(1);
    }
}

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章