6.1 Linux進程概述linux
內核把進程存放在任務隊列(task list)的雙向循環鏈表中,其中鏈表的每一項都是類型爲task_struct,稱爲進程描述符的結構,定義在<include/linux/sched.h>中。數組
task_struct結構中最重要的兩個域:state 和 pid異步
1)進程狀態函數
2)任務標識post
內核經過惟一的進程標識值PID來識別每一個進程。PID是一個非負數,實際是一個短整型數據,最大32768。能夠查看/proc/sys/kernel/pid_max來肯定。spa
進程的建立、執行和終止操作系統
fork() 和 exec()線程
fork()經過複製當前進程的內容建立一個子進程,子進程與父進程的區別僅僅在於不一樣的PID、PPID和其餘一些資源。指針
寫時複製技術。code
exec()函數負責讀取可執行文件並將其載入地址空間開始運行。
優先級在0 ~ MAX_PRIO-1之間,數值越低優先級越高。
實時程序的優先級範圍在0 ~ MAX_RT_PRIO-1,通常進程的優先級在MAX_RT_PRIO ~ MAX_PRIO-1之間。
內核中默認配置:優先級0 ~ 139,實時進程佔用0 ~ 99,通常進程100 ~ 139
一、fork函數
執行一次,返回兩個值。建立一個新進程,稱爲子進程。
父進程返回子進程的進程號,子進程返回0.
#include <sys/types.h> // 提供類型pid_t的定義 #include <unistd.h> pid_t for(void); // 0:子進程 // 子進程ID(大於0的整數):父進程 // -1:出錯
int result = fork(); if (result == -1) { exit(-1); } else if (result ==0) { /*子進程相關語句*/ } else { /*父進程相關語句*/ }
二、exec函數族
取代原調用進程的數據段、代碼段和堆棧段。
#include <unistd.h> int execl(const char *path, const char *arg, ...); int execv(const char *path, char * const argv[]); int execle(const char *path, const char *arg, ..., char *const envp[]); int execve(const char *path, char *const argv[], char *const envp[]); int execlp(const char *file, const char *arg, ...); int execvp(const char *file, char * const argv[]); // 出錯:-1
前4個完整路徑,後2個文件名,從PATH查找。
l(list):逐個列舉,char * arg
v(vector):指針數組,*const argv[]
必須以NULL結尾。
三、exit 和 _exit
exit檢查進程中文件的打開狀況,把文件緩衝區中的內容寫回文件。
_exit直接將進程關閉,緩衝區中的數據會丟失。
exit: #include <stdlib.h> _exit: #include <unistd.h> void exit/_exit(int status); // 0 標識正常
四、wait 和 waitpid
wait函數可使父進程阻塞。直到任意一個子進程結束或者父進程接到了一個指定的信號爲止。若是該父進程沒有子進程或者他的子進程已經結束,則wait函數會當即返回。
wait是waitpid的一個特例。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); // 成功:子進程的進程號 或 0(調用成功但子進程還未退出) // 失敗:-1
status若爲空,則不保存子進程的退出狀態。
五、避免僵死進程實例
當一個進程已經終止,可是其父進程還沒有對其進行回收的進程被稱爲僵死進程。
爲了不,一種辦法是父進程調用wait/waitpid等待子進程結束,可是在子進程結束前父進程會一直阻塞,不能作任何事情。
另外一種更好的方法就是調用兩次fork函數。
System V IPC單機,Socket
Linux中使用較多的進程間通訊方式:
一、無名管道(PIPE)
半雙工、具備固定的讀端和寫端。
一種特殊的文件,存在於內存中。
二、有名管道(FIFO)
可以使互不相關的兩個進程實現彼此通訊。
能夠經過路徑名指出,而且在文件系統中是可見的。
FIFO嚴格地遵循先進先出規則,對管道及FIFO的讀老是從開始處返回數據,對它們的寫則把數據添加到末尾。
有名管道的建立
mkfifo函數,相似於open操做,能夠指定管道的路徑和讀/寫權限。
「mknod 管道名 p」命令來建立有名管道。
管道建立成功後,就可使用open、read、write函數了。與普通文件不一樣的是阻塞問題。
普通文件不阻塞,管道中有阻塞的可能。O_NONBLOCK
#include <sys/types.h> #include <sys/state.h> int mkfifo(const char *filename, mode_t mode); // 成功:0 // 出錯:-1 mkfifo("myfifo", 0666); mkfifo("myfifo", O_RDWR | O_NONBLOCK); // mkfifo僅建立了管道,並無打開管道。
6.3.二、信號通訊
信號是在軟件層次上對中斷機制的一種模擬。
信號是異步的。惟一的異步通訊方式。
兩個來源:硬件來源和軟件來源。
響應:
信號的處理包括信號的發送、捕獲以及信號的處理,
kill 和 raise
raise函數只容許進程向自身發送信號。
#include <signal.h> #include <sys/types.h> int kill(pid_t pid, int sig); int raise(int sig); // 成功:0 // 出錯:-1 raise(SIGSTOP); kill(pis, SIGKILL);
alarm 和 pause
設置一個定時器,到時間時發送SIGALARM信號。一個進程只能有一個鬧鐘時間,新值代替以前的值。
pause函數是用於將調用進程睡眠直到捕捉到信號爲止。一般能夠用來讓進程等待信號到達。
#include <unistd.h> unsigned int alarm(unsigned int seconds) int pause(void); // 成功:上一個時鐘的剩餘時間,不然返回0 // 出錯:-1,而且把error值設爲EINTR ret = alarm(5); pause();
signal
使用signal函數時,只須要把處理的信號和處理函數列出便可。主要用於前32種非實時信號的處理,不支持信號傳遞信息。
#include <signal.h> void (*signal(int signum, /*指定信號*/ void (*handler))(int)))(int) /*對信號的處理*/ // 函數原型 typedef void func(int); func *signal(int, func *); // 成功:之前的信號處理配置 // 出錯:-1
void my_func(int signo) { ... } signal(SIGINT, my_func); signal(SIGQUIT, my_func);
共享內存容許多個進程共享一個給定的內存區域。數據不須要在多個進程之間複製,因此是最高效的一種通訊方式。
使用共享內存的關鍵在於如何在多個進程之間對一給定的存儲區進行同步訪問。
一般信號量被用來實現對共享內存的訪問。
實現步驟:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, /*IPC_PRIVATE*/ int size, /*共享內存區大小*/ int shmflg); /*通open函數的權限位,也能夠用8進製表示法*/ // 成功:共享內存段標識符 // 出錯:-1 void *shmat(int shmid, /*要映射的共享內存區標識符*/ const void *shmaddr, /*將共享內存映射到指定地址(0:由操做系統決定映射地址)*/ int shmflg); /*SHM_RDONLY:共享內存只讀。默認0,可讀寫*/ // 成功:被映射後的地址 // 出錯:-1 int shmdt(const void *shmaddr); /*被映射的共享內存的地址*/ // 成功:0 // 出錯:-1 int shmctl(int shmid, /*共享內存的標識符*/ int cmd, /*要執行的操做的命令字*/ struct shmid_ds *buf); /*獲取/設置共享內存屬性的結構體的地址*/ // 成功:0 // 出錯:-1
/*建立共享內存*/ int shmid = shmget(IPC_PRIVATE, BUFSZ, 0666); /*映射共享內存*/ char *shmaddr = (char *)shmat(shmid, 0, 0); /*查看共享內存對象*/ system("ipcs -m"); /*取消共享內存映射*/ shmdt(shmaddr); /*刪除共享內存*/ shmctl(shmid, IPC_RMID, NULL);
消息隊列由內核維護。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, /*返回新的或已有隊列的隊列ID、IPC_PRIVATE*/ int flag); // 成功:消息隊列ID // 出錯:-1 int msgsnd(int msqid, /*消息隊列的隊列ID*/ const void *prt, /*指向消息結構的指針*/ size_t size, /*消息的大小,不包括消息類型*/ int flag); /*IPC_NOWAIT:若消息並無當即發送而調用進程會當即執行返回。0:阻塞直到消息發送完成爲止*/ // prt消息的結構用戶本身定義,第一個成員的類型必須爲long struct msgbuf { long mtype; /*消息類型*/ char mtext[LEN]; /*消息正文*/ ... } // 成功:0 // 出錯:-1 int msgrcv(int msqid, /*消息隊列的隊列ID*/ const void *msgp, /*接收消息緩衝區*/ int size, /*消息的字節數*/ long msgtype, /*接收的消息類型*/ int flag); /*標誌位*/ // 成功:接收到的消息的字節數 // 失敗:-1 int msgctl(int msqid, int cmd, struct msqid_ds *buf); /*消息隊列緩衝區*/ // 成功:0 // 失敗:-1
typedef struct { long mtype; ... } MSG; MSG msg; /*生成消息隊列須要的key*/ key_t key = ftok(".", 'q'); /*建立消息隊列*/ int msqid = msgget(key, IPC_CREATE|0666); /*添加消息到消息隊列*/ msgsnd(msqid, &msg, sizeof(msg)-sizeof(long), 0); /*讀取消息隊列中第一個消息*/ msgrcv(msqid, &msg, sizeof(msg)-sizeof(long), 0, 0); /*從系統內核中移走消息隊列*/ msgctl(msqid, IPC_RMID, NULL);
pthread線程庫,遵循POSIX規範,具備很好的可移植性。
一、線程建立和退出
#include <pthread.h> int pthread_create(pthread_t *thread, /*線程對象*/ pthread_attr_t *attr, /*線程屬性設置,默認爲NULL,可使用其餘函數來設置*/ void *(*start_routine)(void *), /*線程執行的函數*/ void *arg); /*傳遞給start_routine的參數*/ // 成功:0 // 出錯:-1 void pthread_exit(void *retval); /*線程的返回值,可由其餘函數如pthread_join來檢測獲取*/ int pthread_join(pthread_t thread, void ** thread_return); /*用戶定義的指針,用來存放被子線程的返回值(不爲NULL時)*/ // 成功:0 // 出錯:-1
二、mutex線程訪問控制
線程共享進程的資源和地址空間。POSIX中線程同步的方法主要有互斥鎖和信號量。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, /*指向互斥鎖對象的指針*/ const pthread_mutexattr_t *mutexattr); /*互斥鎖的屬性*/ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex); // 成功:0 // 出錯:-1
mutexattr 若是爲NULL表示按默認屬性建立互斥鎖。
三、信號量線程控制
信號量也就是操做系統中的PV原語。信號量本質上是一個非負的整數計數器,被用來控制對公共資源的訪問。
PV原語是對整數計數器信號量sem的操做。一次P操做使sem減一,一次V操做使sem加一。進程(或線程)根據信號量的值來判斷是否對公共資源具備訪問權限。
當信號量sem的值大於0時,該進程(或線程)具備公共資源的訪問權限;相反,當信號量sem的值等於0時,該進程(或線程)就將阻塞直到信號量sem的值大於0爲止。
PV原語主要用於進程或線程間的同步和互斥這兩種典型狀況。若用於互斥,幾個進程(或線程)每每只設置一個信號量sem。
當信號量用於同步操做時,每每會設置多個信號量,並安排不一樣的初始值來實現他們之間的順序執行。
Linux實現了POSIX的無名信號量,主要用於線程間的互斥同步。
#include <semaphore.h> int sem_init(sem_t *sem, /*信號量*/ int pshated, /*決定信號量可否在幾個進程間共享。因爲目前Linux尚未實現進程間共享信號量,因此這個值只可以取0*/ unsigend int value); /*信號量初始化值*/
#include <pthread.h> int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem); int sem_destroy(sem_t *sem); // 成功:0 // 出錯:-1