SIGCHLD信號
只要子進程信號發生改變,就會產生SIGCHLD信號。
借助SIGCHLD信號回收子進程
回收子進程只跟父進程有關。如果不使用循環回收多個子進程,會產生多個僵尸進程,原因是因為這個信號不會循環等待。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/wait.h>
#include<signal.h>void catch_child(int signo) //有子進程終止,發送sigchld信號時,該函數會被內核回調
{pid_t wpid;int status;//while((wpid = wait(NULL))!= -1);while((wpid = waitpid(-1 , &status , 0))!= 0 ){if(WISEXITED(status))printf("catch child:%d , status: %d\n" ,wpid , WEXITSTATUS(status));}
}int main(int argc , char *argv[])
{pid_t pid;int i ;//阻塞sigset_t set ; // 防止在父進程創建sa_mask之前子進程先死亡。sigemptyset(&set);sigaddset(&set , SIGCHLD);sigprocmask(SIG_BLOCK , &set , NULL);for(i = 0 ; i < 5 ; i++){if((pid = fork()) == 0)break;}if(i == 5){struct sigaction act;act.sa_handler = catch_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD , &act , NULL);//解除阻塞sigprocmask(SIG_UNBLOCK , &set , NULL);printf("I am parent , %d\n" , getpid());}else{printf("I am child , %d\n" , getpid());}return 0 ;
}
中斷系統調用(非重點)
系統調用分為兩類:慢速系統調用和其他系統調用。
慢速系統調用:可能會使進程永遠阻塞的一類。eg. read write wait and so on .
其他系統調用:getpid()....
sa_flags 用來設置被信號中斷后系統調用是否重啟。
進程組和會話
進程組也稱之為作業。
創建會話
會話是多個進程組的集合。
注意事項
創建會話不能是進程組組長,該進程變成新會話首進程。
創建新會話丟棄原有的控制終端,該會話沒有控制終端。
建立新會話時,先調用fork,父進程終止,子進程調用setsid()。
getsid函數
獲取進程所屬的會話id
pid_t getsid(pid_t pid);
成功 返回會話id
失敗 -1
ps ajx命令查看系統中的進程
組長進程不能成為新會話首進程,新會話首進程必定會成為組長進程。?
setsid函數
創建會話,以自己的ID設置進程組ID,同時也是新會話的ID
pid_t setsid(void)
成功:返回調用進程的會話id;
失敗:-1
調用setsid函數的進程,既是新的會長,也是新的組長。
守護進程(daemon精靈,進程)
守護進程時Linux中的后臺服務進程,通常運行與操作系統后臺,脫離控制終端,一般不與用戶直接交互 , 周期性的等待某個事件發生或周期性執行某一動作,不受用戶登入注銷影響。一般使用d結尾的名字。
守護進程創建步驟:
1、fork子進程,讓父進程終止。
2、子進程調用setsid創建新會話。
3、通常根據需要,改變工作目錄位置,chdir() , 防止目錄被卸載
4、通常根據需要,重設置umask文件權限掩碼,影響新文件的創建權限
5、通常根據需要,關閉/重定向 文件描述符
6、守護進程 業務邏輯 while()
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc , char *argv[])
{pid_t pid ;int fd ;pid = fork();if(pid > 0){exit(0); //正常終止父進程}else{pid = setsid(); //創建新會話if(pid == -1){perror("set error");exit(1);}int ret = chdir("/home/qqq"); //改變工作目錄位置if(ret == -1){perror("chdir error");exit(1);}umask(0022); // 設置掩碼權限close(STDIN_FILENO); // 關閉文件描述符fd = open ("/dev/null" , O_RDWR);if(fd == -1){perror("open error");exit(1);}dup2(fd , STDOUT_FILENO); //重定向dup2(fd , STDERR_FILENO);while(1); // 模擬守護進程做的事情}return 0 ;
}
這里由于我不明白為什么需要將標準輸入關閉,并且將標準輸出和標準錯誤重定向,我上網查了一些資料:
1、為什么要關閉STDIN_FILENO?
守護進程是在后臺運行的,不應該與終端交互,因此不需要標準輸入,為防止錯誤,直接關閉。
2、為什么要將STDOUT and STDERR? ?--->fd 0? ("/dev/null")
"/dev/null"是一個特殊設備,寫的什么東西進去都會被吃掉,讀取的時候什么也看不到,守護進程不應該輸出信息到終端,因此重定向給 fd(此時是 /dev/null
)本質上就是為了干凈、不占資源、避免潛在錯誤。
線程(不要將線程和信號混合用)
線程 : LWP,light weight process 輕量級的進程 , 本質仍是進程(Linux環境下)
進程: 獨立的進程地址空間 , 有PCB
線程:有獨立的PCB,但沒有獨立的地址空間(共享)
Linux下 線程是最小的執行單位 , 進程是最小分配資源單位,可以看成只有一個線程的進程。
ps -Lf 進程id? --->線程號 LWP --->CPU執行的最小單位。
線程可以看作寄存器和棧的集合。
線程共享資源
文件描述符表 、 每種信號的處理方式 、 當前工作目錄 、 用戶ID和組ID 、 內存地址空間、全局變量
線程非共享資源
線程id 、 處理器現場和棧指針(內核棧) 、獨立的棧空間(用戶棧) 、 error變量 、 信號屏蔽字
線程的優缺點
優點:提高程序的并發性 ,?開銷小 ,?數據通信、共享數據方便
缺點:庫函數不穩定 , 調試、編寫困難、GDB不支持 , 對信號支持不友好
優點突出,缺點不明顯,能使用線程使用線程
線程控制原語
pthread_self函數
獲取線程ID ,注意這里的線程ID和線程號LWP不一樣。
線程id是在進程地址空間內部,用來表示線程身份id號
pthread_t pthread_self(void);
成功返回 線程id
pthread_create函數
int pthread_create(pthread_t * tid ,const pthread_attr_t *attr , void *(*start_rountn)(void*) , void *arg);pthread_t * tid:傳出參數,傳出創建子線程的id。
const pthread_attr_t *attr:線程屬性 , 傳NULL表示使用默認屬性。
void *(*start_rountn)(void*):子線程回調函數,創建成功,pthread_create()函數返回時,該回調函數 會被自動調用。
void *arg:回調函數的參數,沒有的話使用NULL返回值 成功 0
失敗errno
循環創建5個子線程。?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>void* func(void * arg)
{int i = (int) arg;printf("I am %dth child , pid:%d , tid:%lu\n" , i+1 , getpid() , pthread_self());return NULL;
}int main(int argc , char* argv[])
{pthread_t tid;int i ,ret;for(i = 0 ; i < 5 ; i++){ret = pthread_create(&tid , NULL , func , (void*)i);if(ret == -1){perror("pthread_create error");exit(1);} }sleep(i);printf("I am main , pid:%d , tid:%lu\n" , getpid() , pthread_self());return 0 ;
}
pthread_exit函數
將當前線程退出。
void pthread_exit(void *retval);
retval:退出值。無退出值時,NULLexit() ; 退出當前進程
return ; 返回到調用者那里去
pthread_exit() ; 將調用該函數的線程退出
pthread_join函數
阻塞等待線程退出,獲取線程退出狀態。回收子線程。
int pthread_join(pthread_t thread, void **retval);
返回值 成功 0
失敗 -1
pthread_cancel函數
殺死(取消)線程。相當于kill函數。但是需要取消點(保存點) 。如果子線程沒有到達取消點,那么該函數無效,我們可以在程序中手動添加一個取消點,使用pthread_testcancel()。成功被cancel函數殺死的進程,可以使用pthread_join回收。
int pthread_cancel(pthread_t thread);
返回值: 成功 0
失敗 errno