UNIX環境C語言--進程管理、進程間通訊

******進程管理******
1、基本概念
  一、進程與程序
    進程就是運行中的程序,一個正在運行的程序可能包含多個進程,進程在操做系統中負責執行特定的任務
    程序是存儲在硬盤中的文件,它包含機器指令和數據,是一個靜態的實體
    進程或任務它是處理活動狀態的計算機程序
  二、進程的分類
    a、交互進程:用戶能夠輸入數據、也能看到程序的反饋信息
    b、批處理進程:由系統命令各流程控制語句組成的可執行的腳本文件(Makefile)
    c、守護進程:一直活躍着的進程,通常在後臺運行,由操做系統的開啓腳本或超級用戶加載(init)
  三、查看進程 ps
    ps:簡單顯示當前用戶用控制終端的進程
    a:顯示全部用戶的進程
    -u:以詳細的信息
    -x:包括無控制終端的進程
    -w:以更大的寬度顯示
  四、進程的信息表
    用戶、進程號、cpu佔有率、內存佔有率、虛擬內存佔有率、物理內存使用量、狀態、開始時間、運行時間
  五、進程的狀態
    O 就緒態
    R 運行態
    S 可被喚醒的睡眠
    D 不可被喚醒的睡眠態
    T 暫停,收到了SIGSTOP信號
    X 死亡態
    Z 殭屍態
    < 高優先級
    N 低優先級
    l 多線程進程
  六、若是進程A開啓進程B,那麼進程A就是進程B的父進程,進程B就是進程A的子進程,進程A可能也有父進程,進程B可能也有子進程
  七、子進程結束時會向父進程發送SIGCHLD信號,父進程在收到這個信號後默認選擇忽略,若是父進程沒有及時回收(顯示調用wait),子進程就處於殭屍態
  八、父進程死掉前,會把它的子進程託付給守護進程(init),而後再向他的父進程發送SIGCHLD信號
  九、操做系統會爲每個進程分配一個標識符,可使用getpid獲取,當進程結束後,屬於它的標識符會延時重用

2、getxxid
  getpid 進程標識符
  getppid 父進程標識符
  getuid 進程的實際用戶用戶id
  getgid 進程的實際用戶組id
  geteuid 進程的有效用戶id
  getegid 進程的有效用戶組id

  設置進程的組id標識符和用戶id標識符
  chmod u+s 可執行文件
  chmod g+s 可執行文件
  經過這種方式可讓可執行的擁有者獲取比它本身更高的權限

3、fork
  pid_t fork(void)
  功能:建立一個子進程
    返回值:一次調用兩個返回值
  父進程的分支會返回子進程的進程號,父進程只能在建立子進程的時候獲取子進程的id
  子進程返回0,子進程能隨時獲取父進程的id
  一、經過fork建立的子進程只能經過返回值來分辨父子進程而後進程相應的分支,處理相應的任務
  二、經過fork建立的子進程會拷貝父進程的全局段、靜態數據段、堆、棧、IO流緩衝區、而且父子進程共享代碼段、共享文件描述符、文件指針
  三、fork返回調用成功後,父子進程誰先返回不必定,能夠經過sleep/usleep來確保父子進程誰先執行
  四、當系統中進程的總數超過系統的限制時,fork將調用失敗
  五、練習:使用fork配合sleep實現出殭屍進程和孤兒進程 //zfork.c lfork.c
  六、在fork以前的代碼只有父進程執行,在fork以後的代碼,父進程都有可能執行

4、vfork
  pid_t vfork(void);
  功能:用來建立子進程
    返回值與fork一致
  一、使用vfork建立子進程時,父進程會先暫停,等子進程徹底建立成功以後,父進程再開始執行
  二、使用vfork要和exec函數配合才能建立子進程
  if(0 == vfork())
  {
    exec加載子進程
  }
  父進程
  三、使用vfork不會複製父進程的任何數據,而是經過exec函數加載另外一個可執行文件,這種建立子進程的效率要比fork要高
  四、exec函數不會返回,子進程必定比父進程先執行

  int execl(const char *path, const char *arg, ...);
    path:可執行文件路徑
    arg:給可執行文件的參數,相似於命令行參數,必須以NULL結尾,第一個必須是能夠執行文件名
    execl("path","a.out",NULL);

  一、經過exec建立的子進程會替換掉父進程給的代碼段,不拷貝父進程的棧、堆、全局、靜態數據段,會用新的可執行文件替換掉他們
  二、exec只是加載一個可執行文件,並建立進程,不會產生新的進程號
  三、只有exec函數執行結束(不管成功仍是失敗),父進程才能繼續執行

  int execlp(const char *file, const char *arg, ...);
    file:可執行文件的文件名,會從PATH環境變量指定的位置去找可執行文件
    參數於exec一致

  int execle(const char *path, const char *arg,..., char * const envp[]);
  path和arg與execl一致,但最後要提供環境變量表編程

  int execv(const char *path, char *const argv[]);
  path與execl一致,參數以指針數據的方式提供

  int execvp(const char *file, char *const argv[]);

  int execvpe(const char *file, char *const argv[],char *const envp[]);

5、進程的正常退出
  一、從main中return stats,父進程會獲得stats的低八位
  二、調用exit(stats)函數,父進程會獲得stats的低八位,此函數沒有返回值
  在進程退出前:
    a、調用atexit、onexit註冊的函數
    b、沖刷並關閉打開的文件
    c、再調用_exit/_Exit函數
    stats:EXIT_SUCCESS/EXIT_FAILURE
  三、_exit(stats)/_Exit(stats)函數
    父進程會獲得stats的低八位的數據,進程退出前會託孤,向父進程發送SIGCHLD信號,而且此函數不返回
  四、進程的最後一個線程結束

6、進程的異常退出
  一、進程調用了abort函數,觸發了停止信號
  二、進程收到了某些信號(退出、段錯誤、除0)
  三、最後一個線程收到取消請求,而且對取消請求作出的響應

7、進程的回收wait/waitpid
  #include <sys/types.h>
  #include <sys/wait.h>
  pid_t wait(int *status);
  功能:回收子進程,只要是子進程它都回收
    status:返回進程的結束狀態,以及低八位數據
  要使用系統經過的宏才能解析
  爲NULL時說明不要子進程的結束狀態
  此調用一次只能回收一個子進程,任何一個子進程結束它都會中止並回收子進程,若是想使用它回收全部的子進程必需要不停的調用直到函數返回-1(說明沒有子進程了)
  若是在調用以前就有子進程處於殭屍狀態,會當即返回並回收殭屍子進程

  pid_t waitpid(pid_t pid, int *status, int options);
  功能:要回收指定的子進程
  pid:要回收的子進程的id
    <-1 等待進程組id是pid的絕對值的進程
    -1 等待任意進程結束
    0 等待與父進程同組的子進程結束
    >0 等待指定的子進程結束
  options:若是指定的進程沒有結束,是否阻塞ubuntu

 

******信號處理******
1、基本概念
一、中斷:停止(不是終止)當前程序正在執行的任務,轉而執行其它的任務
硬中斷:由硬件設備觸發的中斷(手機的按鍵)
軟中斷:由其餘程序觸發的中斷(信號,Qt中的信號和槽)

二、不可靠信號
a、小於SIGRTMIN(34)的信號都是不可靠信號(它是創建在早期機制上的一種信號,由怕目標收不到信號會屢次觸發)
b、這種信號不是實時的產生的,也不能夠排隊因此致使信號可能丟失
c、在處理這種信號時能夠選擇默認的處理方式、也能夠註冊一個處理函數(在有些系統中出來函數結束後就恢復成默認的處理方式)

三、可靠信號
a、[SIGRTMIN,SIGRTMAX]範圍內的信號是可靠信號
b、可靠信號支持排隊,不會丟失,實時產生
c、進程與系統之間的通訊都是不可靠信號(當系統察覺到進程觸發一些錯誤時給進程發的都是不可靠信號),在工業控制鄰域通常都使用實時信號

四、信號的來源
硬件:操做系統察覺到硬件工做異常,向正在使用該硬件的進程發送一個信號
軟件:經過kill、raise、alarm等函數或命令產生的信號
鍵盤:
Ctrl+c
Ctrl+\
Ctrl+z

五、信號的處理方式
一、忽略,不作任何處理
二、終止進程
三、捕獲並處理:
當信號發生前向操做系統註冊一個信號處理函數,當信號發生後調用該函數處理信號
四、終止+產生core:
core dump 記錄內存的使用狀況並寫在core文件中,在ubuntu系統中默認不產生core文件,須要經過命令(ulimit- t unlimited)設置
core文件是一個二進制文件,須要相應的調試工具才能解析(gdb)
gcc -g code.c ->a.out
a.out 出現錯誤產生core文件
gdb a.out core 顯示出產生錯誤的代碼

2、signal
#include <signal.h>多線程

typedef void (*sighandler_t)(int);函數

sighandler_t signal(int signum, sighandler_t handler);
功能:想內核註冊一個信號處理函數
signum:要處理的信號
handler:
函數指針:表示該信號捕獲並處理
SIG_IGN:告訴內核不要再向當前進程發送該信號,若是是SIGCHLD,則同時表示當前進程的子進程由init回收
SIG_DEL:恢復默認處理方式
返回值:在設置信號處理方式以前該信號的處理方式

注意:在某些系統中,信號的處理函數只能處理一次,處理函數調用結束後會恢復默認的處理方式,若是想持久處理須要在處理函數即將結束時再註冊一次
SIGKILL、STGSTOP信號不能被忽略、捕獲處理

3、子進程的信號處理
一、經過fork建立的子進程會繼承父進程的信號處理方式
二、經過vfork+exec函數建立子進程沒法繼承父進程的信號處理函數、但會繼承父進程的信號忽略

4、發送信號
一、鍵盤:
Ctrl+c
Ctrl+\
Ctrl+z
二、錯誤:
除0(SIGFPE(8)) 算術異常
非法內存訪問(SIGEGV(11)) 段錯誤
硬件故障(SIGBUS(7)) 總線異常
三、命令
kill -信號 進程號
killall -信號 命令 能夠向多個進程批量的發送信號
四、函數
int kill(pid_t pid,int sig);
功能:向指定的進程發送信號

int rais(int sig);
功能:向本身發送信號

5、pause
int pause(void);
功能:使調用的進程進入睡眠狀態,直到有信號終止該進程或信號被捕獲

一、當信號觸發後,會先執行信號處理函數,pause再返回
二、pause要不不返回(沒有信號觸發),要麼返回-1(有信號產生並處理完畢)

6、sleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:使用調用的進程睡眠seconds秒

一、當進程睡足seconds秒後會有信號產生再返回
二、若是是因爲信號產生中的睡眠,則sleep會返回剩餘的秒數

7、alarm
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:使用調用的進程在seconds後收到SIGALRM信號

一、SIGALRM默認的處理方式是終止進程
二、若是在上次SIGALRM信號產生以前再次設置,會返回剩餘的秒數
三、若是設置的秒數爲0,表示取消以前的設置

8、信號集與信號屏蔽
一、什麼是信號集:信號的集合sigset_t,由128個二進制組成、每個二進制表明一個信號
#include <signal.h>
int sigemptyset(sigset_t *set);
功能:清空信號集工具

int sigfillset(sigset_t *set);
功能:填滿信號集

int sigaddset(sigset_t *set, int signum);
功能:向信號集中添加信號

int sigdelset(sigset_t *set, int signum);
功能:從信號集中刪除信號

int sigismember(const sigset_t *set, int signum);
功能:測試信號集中是否有某個信號
返回值:有返回1,沒有返回0,失敗返回-1

二、信號屏蔽
每個進程都有一個信號掩碼(signal mask),也叫信號屏蔽碼,它是一個信號集,其中包含了須要屏蔽的信號
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:設置進程新的信號掩碼(信號屏蔽碼),獲取舊的信號屏蔽碼
how:修改信號掩碼的方式
SIG_BLOCK:向當前信號掩碼中添加信號
SIG_UNBLOCK:從當前信號掩碼中刪除信號
SIG_SETMASK:用新的信號集替舊的信號掩碼
newset:新添加、刪除、替換的信號集,也能夠爲空,由how說了算
oldset:獲取舊的信號掩碼
當newset爲空時,就是在備份信號掩碼

爲何要信號屏蔽:
當進程執行一些敏感操做時不但願被打擾(原子操做),但又不但願信號丟失(忽略),此時須要屏蔽信號
屏蔽信號的目的不是爲了避免接收信號,而是延遲接收,當處理完要作的事情後,應該把屏蔽的信號還原
當信號屏蔽時發生的信號會記錄一次,這個信號設置爲未決狀態,當信號屏蔽結束後,會再發送一次
不可靠信號在信號屏蔽期間不管信號發送多少次,信號解除屏蔽後,只發送一次
可靠信號在信號屏蔽期間發生的信號會排隊記錄,在信號接觸屏蔽後逐個處理
在執行信號處理函數時,會默認把當前處理的信號屏蔽掉,執行完成後再恢復

int sigpending(sigset_t *set);
功能:獲取未決狀態的信號
能夠在解除信號屏蔽前預先查找有哪些未決的信號

9、信號處理
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:能夠忽略信號、設置或獲取信號處理方式
act:設置心的信號處理方式
oldact:獲取舊的信號處理方式

struct sigaction
{
void (*sa_handler)(int); //信號處理函數指針
void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理函數指針 須要使用sigqueue發送信號
sigset_t sa_mask; //信號屏蔽碼,執行信號處理函數時被屏蔽的信號(在執行信號處理函數時,默認屏蔽正在處理的函數,也能夠添加其它要屏蔽的信號,在信號處理函數結束後恢復成默認)
int sa_flags;
SA_NOCLDSTOP:忽略SIGCHLD
SA_NODEFER/SA_NOMASK:處理時不屏蔽信號
SA_RESETHAND:處理完信號後,恢復默認處理方式
SA_RESTART:當信號處理函數中斷的系統調用,則重啓
SA_SIGINFO:用sa_sigaction處理信號
void (*sa_restorer)(void);//保留
};

int sigqueue(pid_t pid, int sig, const union sigval value);
功能:向指定的進程發送信號,並附帶一些數據

10、計時器
操做系統維護了三個計時器
真實計時器:程序運行的真實時間
虛擬計時器:記錄程序在用戶態耗費的時間
實用計時器:記錄程序在用戶態和內核態耗費的時間
真實 = 實用 + 進出的耗費 + 休眠

使用計時器定時作一些事情,相似鬧鐘的功能
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
功能:獲取以前設置的定時任務
which:計時器的類型
ITIMER_REAL 真實,信號:SIGALRM
ITIMER_VIRTUAL 虛擬,信號:SIGVTALRM
ITIMER_PROF 實用,信號:SIGPROF
curr_value:
struct timeval it_interval:時鐘信號的間隔時間
struct timeval it_value:第一次時鐘信號產生的時間

struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};測試

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
功能:設置、獲取鬧鐘
new_value:新設置的鬧鐘
NULL說明只能用來獲取舊鬧鐘
old_value
NULL說明只設置不獲取

******進程間通訊******
1、基本概念
進程間通訊(IPC):進程之間交換數據的過程叫進程間通訊
進程間通訊的方式
簡單的進程間通訊:
命令行:父進程經過exec函數建立子進程時能夠附加一些數據
環境變量:父進程經過exec函數建立子進程順便傳遞一張環境變量表
信號:父子進程之間能夠根據進程號相互發送信號,進行簡單通訊
文件:一個進程向文件中寫入數據,另外一個進程從文件中讀取出來
命令行、環境變量只能單向傳遞,信號太過於簡單,文件通訊不能實時

2、管道
傳統的進程間通訊方式:管道
一、管道是一種古老的通訊的方式(基本上再也不使用)
二、早期的管道是一種半雙工,如今大多數是全雙工
三、有名管道(這種管道是以文件方式存在的),適合任意進程之間的通訊
建立管道文件:
命令mkfifo
函數mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
管道通訊的編程模式:
進程A 進程B
建立管道mkfifo
打開管道open 打開管道
寫/讀數據 read/write 讀/寫數據
關閉管道close 關閉管道
刪除unlink/remove
四、無名管道:由內核幫助建立,只返回管道的文件描述符,看不到管道文件,但這種管道只能用在fork建立的父子進程之間
#include <unistd.h>
int pipe(int pipefd[2]);
功能:返回兩個打開的管道文件描述符
pipefd[0] 用來讀數據
pipefd[1] 用來寫數據

3、XSI IPC進程間通訊
一、XSI通訊是靠內核的IPC對象進行通訊
二、每個IPC對象都有一個IPC標識符(相似文件描述符),IPC標識符它是一個非零整數
三、IPC對象必需要先建立,建立後才能進程獲取、設置、操做、刪除、銷燬
四、建立IPC對象必需要提供一個鍵值(key_t),健值是建立、獲取IPC對象的依據
五、產生健值的方法:
固定的字面值:1980014
使用函數計算:健值 = ftok(項目路徑,項目id)
使用宏讓操做系統隨機分配:IPC_PRIVTE
必須把獲取到的IPC對象標識符記錄下來,告訴其它進程
六、XSI能夠建立的IPC對象有:共享內存、消息隊列、信號量

4、共享內存
一、由內核維護一塊共享的內存區域,其它進程把本身的虛擬地址映射到這快內存,而後多個進程之間就能夠共享這快內存了
二、這種進程間通訊的好處是不須要信息複製,是進程間通訊最快的一種方式
三、但這種通訊方式會面臨同步的問題,須要與其它通訊方式配合,最合適的就是信號

共享內存的編程模式:
一、進程之間要約定一個鍵值
進程A 進程B
建立共享內存
加載共享內存 加載共享內存
卸載共享內存 卸載共享內存
銷燬共享內存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:建立共享內存(會在內核中開闢一塊內存),若是要建立的對象已經存在,能夠出錯,也能夠獲取
size:共享的大小,儘可能是4096的倍數
shmflg:
建立:IPC_CREAT | IPC_EXCL |0744
返回值:IPC對象標識符(相似文件描述符)

void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加載共享內存(進程的虛擬地址與內核中的共享內存映射)
shimid:shmget的返回值
shmaddr:進程提供的虛擬地址,若是爲NULL,操做系統會自動選擇一塊地址映射
shmflg:
SHM_RDONLY:限制內存的權限爲只讀
SHM_REMAP:映射已經存的共享內存
SHM_RND:當shmaddr爲空時自動分配
SHMLBA:shmaddr的值不能爲空,不然出錯
0
返回值:映射後的內存的虛擬地址
int shmdt(const void *shmaddr);
功能:卸載共享內存(進程的虛擬地址與共享的內存取消映射關係)

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制/銷燬共享內存
cmd:
IPC_STAT:獲取共享內存的屬性
IPC_SET:設置共享內存的屬性
IPC_RMID:刪除共享內存
IPC_INFO:獲取關係內存的信息
buf:記錄共享內存屬性的對象

注意:關係內存是進程間通訊方式中最快的一種,由於數據沒有複製過程,可是進程之間沒法得知數據的寫入和讀取,須要其它的通訊方式配合(信號)

消息隊列的特色是數據能夠排隊,能夠按消息類型接受消息

信號量能夠看做是進程間共享的全局變量,用來管理進程之間共享的資源

5、消息隊列
一、消息隊列是一個由系統內核負責存儲和管理、並經過IPC對象標識符獲取的數據鏈表
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:建立和獲取消息隊列
msgflg:
建立:IPC_CREAT | IPC_EXEC | 0644
獲取:0

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息隊列發送消息
msgid:msgget的返回值
msgp:消息(消息類型+消息內容)的首地址
msgsz:消息內存的長度(不包括消息類型)
msgflg:
MSG_NOERROR:當消息的實際長度比msgsz還要長的話則按照msgsz長度截取再發送,不然產生錯誤

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:從消息隊列接受消息
msgp:存儲消息的緩衝區
msgsz:要接收的消息長度
msgtyp:消息的類型(它包含在消息的前4個字節)
msgflg:
MSG_NOWAIT:若是要接收的消息不存在,直接返回,不然消息阻塞等待
MSG_EXCEPT:從消息隊列中接收第一個不msgtyp類型的第一個消息

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:控制、銷燬消息隊列
cmd:
IPC_STAT:獲取消息隊的屬性
IPC_SET:設置消息隊列的屬性
IPC_RMID:刪除消息隊列

6、IPC相關命令
ipcs -m 查找共享內容
ipcrm -m id 刪除共享內存
ipcs -q 察看消息隊列
ipcrm -q id 刪除消息隊列
ipcs -s 察看信號量
ipcrm -s 刪除信號量

7、信號量
信號量(信號燈),能夠看成進程與進程之間共享的全局變量,通常用來爲共享的資源計數
信號量的使用方法:
一、進程A,建立信號量,並設置初始化(設置資源的數)
二、進程B,獲取信號量,查看信號量(查詢剩餘資源的數量),減小信號量(使用資源),增長信號量(資源使用完畢歸還)
三、當一進程嘗試減小信號量,若是不能減,(資源使用完畢),則進程能夠進入等待狀態,當信號量可以被減時(其餘進程把資源還回來了),進程會被喚醒

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:建立信號量或獲取信號量
nsems:信號量的數量
semflg:
IPC_CREAT | IPC_EXEC | 0644

int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:對信號量增長或減小
struct sembuf
{
unsigned short sem_num; 信號量的編號 /* semaphore number */
short sem_op; 對信號量的操做 /* semaphore operation */
short sem_flg; 信號量是否等待 /* operation flags */ui

}
nsops:操做數量

int semctl(int semid, int semnum, int cmd, ...);
功能:對信號量控制或釋放
semnum:信號量的編號
cmd:
IPC_SET 設置信號量的屬性
IPC_STAT 獲取信號量的屬性
IPC_RMID 刪除信號量
IPC_INFO 獲取信號量信息
GETVAL 返回信號量數量
SETVAL 設置信號量數量
返回值:信號量的數量操作系統

相關文章
相關標籤/搜索