Linux进程编程基础介绍
Linux系統(tǒng)是一個多進(jìn)程的系統(tǒng),它的進(jìn)程之間具有并行性、互不干擾等特點。也就是說,每個進(jìn)程都是一個獨(dú)立的運(yùn)行單位,擁有各自的權(quán)利和責(zé)任。其中,各個進(jìn)程都運(yùn)行在獨(dú)立的虛擬地址空間,因此,即使一個進(jìn)程發(fā)生異常,它也不會影響到系統(tǒng)中的其他進(jìn)程。
Linux進(jìn)程是一個具有獨(dú)立功能的程序關(guān)于某個數(shù)據(jù)集合的一次可以并發(fā)執(zhí)行的運(yùn)行活動,是處于活動狀態(tài)的計算機(jī)程序。進(jìn)程作為構(gòu)成系統(tǒng)的基本細(xì)胞,不僅是系統(tǒng)內(nèi)部獨(dú)立運(yùn)行的實體,而且是獨(dú)立競爭資源的基本實體。
Linux進(jìn)程是一個程序的一次執(zhí)行的過程,同時也是資源分配的最小單元。它和程序是有本質(zhì)區(qū)別的,程序是靜態(tài)的,它是一些保存在磁盤上的指令的有序集合,沒有任何執(zhí)行的概念;進(jìn)程是一個動態(tài)的概念,它是程序執(zhí)行的過程,包括了動態(tài)創(chuàng)建、調(diào)度和消亡的整個過程。它是程序執(zhí)行和資源管理的最小單位。
進(jìn)程是程序的執(zhí)行過程,根據(jù)它的生命周期可以劃分成3種狀態(tài):(1)、執(zhí)行態(tài):該進(jìn)程正在運(yùn)行,即進(jìn)程正在占用CPU;(2)、就緒態(tài):進(jìn)程已經(jīng)具備執(zhí)行的一切條件,正在等待分配CPU的處理時間片;(3)、等待態(tài)(阻塞態(tài)):進(jìn)程不能使用CPU,若等待事件發(fā)生(等待的資源分配到)則可將其喚醒。
導(dǎo)致進(jìn)程終止的三種情況:正常終止、異常終止、外部干擾。
終止進(jìn)程的主要操作過程如下;(1)、找到指定進(jìn)程的PCB;(2)、終止該進(jìn)程的運(yùn)行;(3)、回收該進(jìn)程所占用的全部資源;(4)、終止其所有子孫進(jìn)程,回收它們所占用的全部資源;(5)、將被終止進(jìn)程的PCB從原來隊列中摘走。
進(jìn)程阻塞的過程如下:(1)、立即停止當(dāng)前進(jìn)程的執(zhí)行;(2)、現(xiàn)行進(jìn)程的CPU現(xiàn)場保存;(3)、現(xiàn)行狀態(tài)由“運(yùn)行”改為“阻塞”;(4)、轉(zhuǎn)到進(jìn)程調(diào)度程序。
Linux 系統(tǒng)的進(jìn)程狀態(tài)模型的各種狀態(tài):用戶狀態(tài)、內(nèi)核狀態(tài)、內(nèi)存中就緒、內(nèi)存中睡眠、就緒且換出、睡眠且換出、被搶先、創(chuàng)建狀態(tài)、僵死狀態(tài)。
進(jìn)程的上下文是由用戶級上下文、寄存器上下文以及系統(tǒng)級上下文組成。主要內(nèi)容是該進(jìn)程用戶空間內(nèi)容、寄存器內(nèi)容以及與該進(jìn)程有關(guān)的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。當(dāng)系統(tǒng)收到一個中斷、執(zhí)行系統(tǒng)調(diào)用或內(nèi)核做上下文切換時,就會保存進(jìn)程的上下文。一個進(jìn)程是在它的上下文中運(yùn)行的,若要調(diào)度進(jìn)程,就要進(jìn)行上下文切換。
在Linux系統(tǒng)中,用戶創(chuàng)建一個進(jìn)程的唯一方法就是使用系統(tǒng)調(diào)用fork。
Linux系統(tǒng)調(diào)用exit,是進(jìn)程用來終止執(zhí)行時調(diào)用的。進(jìn)程發(fā)出該調(diào)用,內(nèi)核就會釋放該進(jìn)程所占的資源,釋放進(jìn)程上下文所占的內(nèi)存空間,保留進(jìn)程表項,將進(jìn)程表項中紀(jì)錄進(jìn)程狀態(tài)的字段設(shè)為僵死狀態(tài)。內(nèi)核在進(jìn)程收到不可捕捉的信號時,會從內(nèi)核內(nèi)部調(diào)用exit,使得進(jìn)程退出。父進(jìn)程通過wait得到其子進(jìn)程的進(jìn)程表項中記錄的計時數(shù)據(jù),并釋放進(jìn)程表項。最后,內(nèi)核使得進(jìn)程1(init 進(jìn)程,init進(jìn)程是系統(tǒng)所有進(jìn)程的起點,它的進(jìn)程號是1。)接收終止執(zhí)行的進(jìn)程的所有子進(jìn)程。如果有子進(jìn)程僵死,就向init 進(jìn)程發(fā)出一個SIGCHLD 的軟中斷信號。
一個進(jìn)程通過調(diào)用wait來與它的子進(jìn)程同步,如果發(fā)出調(diào)用的進(jìn)程沒有子進(jìn)程則返回一個錯誤,如果找到一個僵死的子進(jìn)程就取子進(jìn)程的PID及退出時提供給父進(jìn)程的參數(shù)。如果有子進(jìn)程,但沒有僵死的子進(jìn)程,發(fā)出調(diào)用的進(jìn)程就睡眠在一個可中斷的級別上,直到收到一個子進(jìn)程僵死(SIGCLD)的信號或其他信號。
進(jìn)程控制的一個主要內(nèi)容就是對其他程序引用。該功能是通過系統(tǒng)調(diào)用exec來實現(xiàn)的,該調(diào)用將一個可執(zhí)行的程序文件讀入,代替發(fā)出調(diào)用的進(jìn)程執(zhí)行。內(nèi)核讀入程序文件的正文,清除原先進(jìn)程的數(shù)據(jù)區(qū),清除原先用戶軟中斷信號處理函數(shù)的地址,當(dāng)exec調(diào)用返回時,進(jìn)程執(zhí)行新的正文。
Linux系統(tǒng)是一個分時系統(tǒng),內(nèi)核給每個進(jìn)程分一個時間片,該進(jìn)程的時間片用完就會調(diào)度另一個進(jìn)程執(zhí)行。
進(jìn)程調(diào)度分成兩個部分,一個是調(diào)度的時機(jī),即什么時候調(diào)度;一個是調(diào)度的算法,即如何調(diào)度和調(diào)度哪個進(jìn)程。
fork函數(shù)啟動一個新的進(jìn)程,這個進(jìn)程幾乎是當(dāng)前進(jìn)程的一個拷貝:子進(jìn)程和父進(jìn)程使用相同的代碼段;子進(jìn)程復(fù)制父進(jìn)程的堆棧段和數(shù)據(jù)段。這樣,父進(jìn)程的所有數(shù)據(jù)都可以留給子進(jìn)程,但是,子進(jìn)程一旦開始運(yùn)行,雖然它繼承了父進(jìn)程的一切數(shù)據(jù),但實際上數(shù)據(jù)卻已經(jīng)分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數(shù)據(jù)了。它們再要交互信息時,只有通過進(jìn)程間通信來實現(xiàn)。
對于父進(jìn)程,fork函數(shù)返回了子程序的進(jìn)程號,而對于子程序,fork函數(shù)則返回零。在操作系統(tǒng)中,我們用ps函數(shù)就可以看到不同的進(jìn)程號,對父進(jìn)程而言,它的進(jìn)程號是由比它更低層的系統(tǒng)調(diào)用賦予的,而對于子進(jìn)程而言,它的進(jìn)程號即是fork函數(shù)對父進(jìn)程的返回值。
實際執(zhí)行fork時,物理空間上兩個進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個進(jìn)程寫了某個數(shù)據(jù)時,這時兩個進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別的"頁"從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小。
系統(tǒng)調(diào)用exec是用來執(zhí)行一個可執(zhí)行文件來代替當(dāng)前進(jìn)程的執(zhí)行映像。需要注意的是,該調(diào)用并沒有生成新的進(jìn)程,而是在原有進(jìn)程的基礎(chǔ)上,替換原有進(jìn)程的正文,調(diào)用前后是同一個進(jìn)程,進(jìn)程號PID不變。但執(zhí)行的程序變了(執(zhí)行的指令序列改變了)。
系統(tǒng)調(diào)用exec和fork經(jīng)常結(jié)合使用,父進(jìn)程fork一個子進(jìn)程,在子進(jìn)程中調(diào)用exec來替換子進(jìn)程的執(zhí)行映像,并發(fā)的執(zhí)行一些操作。
系統(tǒng)調(diào)用exit的功能是終止發(fā)出調(diào)用的進(jìn)程。
系統(tǒng)調(diào)用wait的功能是發(fā)出調(diào)用的進(jìn)程只要有子進(jìn)程,就睡眠直到它們中的一個終止為止。
函數(shù)調(diào)用sleep可以用來使進(jìn)程掛起指定的秒數(shù)。
獲得進(jìn)程相關(guān)的ID:與進(jìn)程相關(guān)的ID有,(1)、真正用戶標(biāo)識號(UID):該標(biāo)識號負(fù)責(zé)標(biāo)識運(yùn)行進(jìn)程的用戶;(2)、有效用戶標(biāo)識號(EUID):該標(biāo)識號負(fù)責(zé)標(biāo)識以什么用戶身份來給新創(chuàng)建的進(jìn)程賦所有權(quán)、檢查文件的存取權(quán)限和檢查通過系統(tǒng)調(diào)用kill向進(jìn)程發(fā)送軟中斷信號的許可權(quán)限;(3)、真正用戶組標(biāo)識號(GID):負(fù)責(zé)標(biāo)識運(yùn)行進(jìn)程的用戶所屬的組ID;(4)、有效用戶組標(biāo)識號(EGID):用來標(biāo)識目前進(jìn)程所屬的用戶組。可能因為執(zhí)行文件設(shè)置set-gid位而與gid不同;(5)、進(jìn)程標(biāo)識號(PID):用來標(biāo)識進(jìn)程;(6)、進(jìn)程組標(biāo)識號(process group ID):一個進(jìn)程可以屬于某個進(jìn)程組。可以發(fā)送信號給一組進(jìn)程。注意,它不同與gid。
如果要獲得進(jìn)程的用戶標(biāo)識號,用getuid調(diào)用。調(diào)用geteuid是用來獲得進(jìn)程的有效用戶標(biāo)識號。
如果要獲得運(yùn)行進(jìn)程的用戶組ID,使用getgid調(diào)用來獲得真正的用戶組ID,用getegid獲得有效的用戶組ID。
如果要獲得進(jìn)程的ID,使用getpid調(diào)用;要獲得進(jìn)程的父進(jìn)程的ID,使用getppid調(diào)用。
如果要獲得進(jìn)程所屬組的ID,使用getpgrp調(diào)用;若要獲得指定PID進(jìn)程所屬組的ID用getpgid調(diào)用。
調(diào)用setuid為當(dāng)前發(fā)出調(diào)用的進(jìn)程設(shè)置真正和有效用戶ID。參數(shù)uid是新的用戶標(biāo)識號(該標(biāo)識號應(yīng)該在/etc/passwd文件中存在)。調(diào)用setgid設(shè)置當(dāng)前發(fā)出調(diào)用的進(jìn)程的真正、有效用戶組ID。該調(diào)用允許進(jìn)程指定進(jìn)程的用戶組ID為參數(shù)gid,如果進(jìn)程的有效用戶ID不是超級用戶,該參數(shù)gid必須等于真正用戶組ID、有效用戶組ID中的一個。
調(diào)用setpgrp用來將發(fā)出調(diào)用的進(jìn)程的進(jìn)程組ID設(shè)置成與該進(jìn)程的PID相等。注意,以后由這個進(jìn)程派生的子進(jìn)程都擁有該進(jìn)程組ID(除非修改子進(jìn)程的進(jìn)程組ID)。調(diào)用setpgid用來將進(jìn)程號為參數(shù)pid的進(jìn)程的進(jìn)程組ID設(shè)定為參數(shù)pgid。如果參數(shù)pid為0,則修改發(fā)出調(diào)用進(jìn)程的進(jìn)程組ID。
chdir是用來將進(jìn)程的當(dāng)前工作目錄改為由參數(shù)指定的目錄。
系統(tǒng)調(diào)用chroot用來改變發(fā)出調(diào)用進(jìn)程的根(“/”)目錄。
系統(tǒng)調(diào)用nice用來改變進(jìn)程的優(yōu)先權(quán)。
所謂僵尸進(jìn)程,是指使用fork后,子進(jìn)程先于父進(jìn)程結(jié)束,但是因為父子進(jìn)程間依然有關(guān)系,那么子進(jìn)程實際上不會真正意義上終結(jié),如果查看當(dāng)前進(jìn)程表,會發(fā)現(xiàn)該進(jìn)程依然存在。僵尸進(jìn)程是非常特殊的一種,它已經(jīng)放棄了幾乎所有內(nèi)存空間,沒有任何可執(zhí)行代碼,也不能被調(diào)度,僅僅在進(jìn)程列表中保留一個位置,記載該進(jìn)程的退出狀態(tài)等信息供其他進(jìn)程收集,除此之外,僵尸進(jìn)程不再占有任何內(nèi)存空間。
系統(tǒng)對一個用戶可以同時運(yùn)行的進(jìn)程數(shù)是有限制的,對超級用戶沒有該限制,但也不能超過進(jìn)程表的最大表項的數(shù)目。
Linux下一個進(jìn)程在內(nèi)存里有三部的數(shù)據(jù),就是"代碼段"、"堆棧段"和"數(shù)據(jù)段"。這三個部分也是構(gòu)成一個完整的執(zhí)行序列的必要的部分。"代碼段",顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個進(jìn)程運(yùn)行相同的一個程序,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc 之類的函數(shù)取得的空間)。系統(tǒng)如果同時運(yùn)行數(shù)個相同的程序,它們之間就不能使用同一個堆棧段和數(shù)據(jù)段。
test_fork1.cpp:
//fork函數(shù)的使用
//輸出結(jié)果的順序和進(jìn)程調(diào)度的順序有關(guān)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>extern int errno;int main()
{char buf[100];pid_t cld_pid;int fd;int status;if ((fd = open("temp", O_CREAT|O_TRUNC|O_RDWR,S_IRWXU)) == -1) {printf("open error %d\n",errno);exit(1);}strcpy(buf, "This is parent process write\n");if ((cld_pid = fork()) == 0) { /* 這里是子進(jìn)程執(zhí)行的代碼 */strcpy(buf, "This is child process write\n");printf("This is child process\n");printf("My PID(child) is %d\n", getpid()); /*打印出本進(jìn)程的ID*/printf("My parent PID is %d\n", getppid()); /*打印出父進(jìn)程的ID*/write(fd, buf, strlen(buf));close(fd);exit(0);} else { /* 這里是父進(jìn)程執(zhí)行的代碼 */printf("This is parent process\n");printf("My PID(parent) is %d\n",getpid());/*打印出本進(jìn)程的ID*/printf("My child PID is %d\n", cld_pid);/*打印出子進(jìn)程的ID*/write(fd, buf, strlen(buf));close(fd);}//父子進(jìn)程是彼此相互獨(dú)立運(yùn)行的,所以要想讓父進(jìn)程等待子進(jìn)程,只需使用wait()系統(tǒng)調(diào)用。wait(&status); /* 如果此處沒有這一句會如何?*/return 0;
}test_fork2.cpp:
//fork的使用
//屏幕上交替出現(xiàn)子進(jìn)程與父進(jìn)程各打印出的一千條信息
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{int i;if (fork() == 0) {/* 子進(jìn)程程序 */for (i = 1; i <1000; i ++)printf("This is child process\n");} else {/* 父進(jìn)程程序*/for (i = 1; i <1000; i ++)printf("This is parent process\n");}return 0;
}test_fork3.cpp:
//waitpid的使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main()
{pid_t pc, pr;pc=fork();if (pc<0) /* 如果fork 出錯 */printf("Error occured on forking.\n");else if (pc == 0) {/* 如果是子進(jìn)程 */sleep(10);/* 睡眠10 秒 *///exit(0);return 0;}/* 如果是父進(jìn)程 */do {pr = waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG 參數(shù),waitpid 不會在這里等待 */if (pr == 0) {/* 如果沒有收集到子進(jìn)程 */printf("No child exited\n");sleep(1);}} while (pr == 0); /* 沒有收集到子進(jìn)程,就回去繼續(xù)嘗試 */if (pr == pc)printf("successfully get child %d\n", pr);elseprintf("some error occured\n");return 0;
}test_fork4.cpp:
//fork的使用
//此代碼來自:http://www.linuxidc.com/Linux/2013-06/85903p6.htm
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>int main()
{pid_t child1, child2, child;/*先創(chuàng)建子進(jìn)程1*/child1 = fork();/*子進(jìn)程1的出錯處理*/if (child1 == -1) {printf("Child1 fork error\n");exit(1); /*異常退出*/} else if(child1 == 0) { /*在子進(jìn)程1中調(diào)用execlp()函數(shù)*/printf("I am child1 and I execute 'ls -l'\n");if (execlp("ls", "ls", "-l", NULL) < 0) {printf("Child1 execlp error\n");}} else {/*在父進(jìn)程中再創(chuàng)建進(jìn)程2,然后等待兩個子進(jìn)程的退出*/child2 = fork();/*子進(jìn)程2的出錯處理*/if (child2 == -1) {printf("Child2 fork error\n");exit(1);} else if (child2 == 0) {/*在子進(jìn)程2中使其暫停5s*/printf("I am child2. I will sleep for 5 seconds!\n");sleep(5);printf("I am child2. I have awaked and I will exit!\n");exit(0);}printf("I am father progress\n");child = waitpid(child1, NULL, 0);/*阻塞式等待*/if (child == child1) printf("I am father progress. I get child1 exit code:%d\n", child);elseprintf("Error occured!\n");do {child = waitpid(child2, NULL, WNOHANG);/*非阻塞式等待*/if (child == 0) {printf("I am father progress. The child2 progress has not exited!\n");sleep(1);}} while (child == 0);if (child == child2)printf("I am father progress. I get child2 exit code:%d\n",child);elseprintf("Erroe occured!\n");}exit(0);
}
管道和信號是進(jìn)程間通信的兩種機(jī)制。操作系統(tǒng)中的每一個管道有兩個文件描述符,一個文件描述符用來讀,另一個用來寫。信號是一個軟件中斷,主要用于進(jìn)程間異步事件通知與進(jìn)程控制。
進(jìn)程間的通信類型有6種:(1)、管道(pipe)和有名管道(FIFO);(2)、信號(signal);(3)、共享內(nèi)存;(4)、消息隊列;(5)、信號量;(6)、套接字(socket)。
進(jìn)程間通信目的有5種:(1)、數(shù)據(jù)傳輸:一個進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個進(jìn)程;(2)、共享數(shù)據(jù):多個進(jìn)程想要共享數(shù)據(jù),一個進(jìn)程對共享數(shù)據(jù)進(jìn)行修改后,別的進(jìn)程可以立刻看到;(3)、通知事件:一個進(jìn)程需要向另一個或一組進(jìn)程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進(jìn)程終止時要通知父進(jìn)程);(4)、資源共享:多個進(jìn)程之間共享同樣的資源,為了做到這一點,需要內(nèi)核提供鎖和同步機(jī)制;(5)、進(jìn)程控制:有些進(jìn)程希望完全控制另一個進(jìn)程的執(zhí)行(如Debug進(jìn)程),此時控制進(jìn)程希望能夠攔截另一個進(jìn)程的所有陷入和異常,并能夠及時知道它的狀態(tài)改變。
管道是Linux中最常用的進(jìn)程間通信的IPC機(jī)制。使用管道時,一個進(jìn)程的輸出可成為另外一個進(jìn)程的輸入。當(dāng)輸入/輸出的數(shù)據(jù)量特別大時,管道這種IPC機(jī)制非常有用。
在Linux中,通過將兩個file結(jié)構(gòu)指向同一個臨時的VFS索引節(jié)點,而兩個VFS索引節(jié)點又指向同一個物理頁而實現(xiàn)管道。
管道允許在進(jìn)程之間按先進(jìn)先出的方式傳送數(shù)據(jù),管道也能使進(jìn)程同步執(zhí)行。管道傳統(tǒng)的實現(xiàn)方法是通過文件系統(tǒng)作為存儲數(shù)據(jù)的地方。有兩種類型的管道:一種是無名管道,簡稱為管道;另一種是有名管道,也稱為FIFO。進(jìn)程使用系統(tǒng)調(diào)用open來打開有名管道,使用系統(tǒng)調(diào)用pipe來建立無名管道。使用無名管道通訊的進(jìn)程,必須是發(fā)出pipe調(diào)用的進(jìn)程及其子進(jìn)程。使用有名管道通訊的進(jìn)程沒有上述限制。
系統(tǒng)調(diào)用dup是用來復(fù)制一個文件描述符,也就是將進(jìn)程u區(qū)的文件描述符表中的一項復(fù)制一份,使得這兩項同時指向系統(tǒng)文件表的同一表項。
pipe管道:若要創(chuàng)建一個簡單的管道,可以使用系統(tǒng)調(diào)用pipe(),它接收一個參數(shù),也就是一個包括兩個整數(shù)的數(shù)組。如果系統(tǒng)調(diào)用成功,此數(shù)組將包括管道使用的兩個文件描述符,一個為讀端,一個為寫端。pipe管道是半雙工的,數(shù)據(jù)只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;只能用于父子進(jìn)程或者兄弟進(jìn)程之間。管道主要用于父子進(jìn)程間通信。實際上,通常先創(chuàng)建一個管道,再通過fork函數(shù)創(chuàng)建一個子進(jìn)程,其實pipe的兩個文件描述符指向相同的內(nèi)存空間,只不過filedes[1]有寫權(quán)限,filedes[0]有讀權(quán)限。
由于進(jìn)程的文件描述符0指向標(biāo)準(zhǔn)輸入(鍵盤),1指向標(biāo)準(zhǔn)輸出(屏幕),2指向標(biāo)準(zhǔn)錯誤(屏幕),所以進(jìn)程可用的文件描述符從3開始。進(jìn)程是通過對文件描述符的操作從而實現(xiàn)對文件描述符所指向文件的操作。
標(biāo)準(zhǔn)流管道:管道的操作也支持文件流模式,這種管道稱為標(biāo)準(zhǔn)流管道。標(biāo)準(zhǔn)流管道通過popen()創(chuàng)建一個管道popen()會調(diào)用fork()產(chǎn)生一個子進(jìn)程,執(zhí)行一個shell以運(yùn)行命令來開啟一個進(jìn)程,并把執(zhí)行結(jié)果寫入管道中,然后返回一個文件指針。程序通過文件指針可讀取管道中的內(nèi)容。使用popen()創(chuàng)建的標(biāo)準(zhǔn)流管道,需要用pclose()進(jìn)行關(guān)閉。
命名管道(FIFO):和一般的管道基本相同,但也有一些顯著的不同,(1)、命名管道是在文件系統(tǒng)中作為一個特殊的設(shè)備文件而存在的;(2)、不同祖先的進(jìn)程之間可以通過命名管道共享數(shù)據(jù);(3)、當(dāng)共享命名管道的進(jìn)程執(zhí)行完所有的I/O操作以后,命名管道將繼續(xù)保存在文件系統(tǒng)中,以便以后使用;(4)、普通管道只能由父子兄弟等相關(guān)進(jìn)程使用,它們共同的祖先進(jìn)程創(chuàng)建了管道。但是,通過命名管道,不相關(guān)的進(jìn)程也能交換數(shù)據(jù);(5)、一旦已經(jīng)用mkfifo函數(shù)創(chuàng)建了一個FIFO,就可用open打開它。實際上,一般的文件I/O函數(shù)(close、read、write、unlink等)都可用于FIFO。
test_pipe1.cpp:
//pipe管道的使用
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>void look_into_pipe()
{int n;int fd[2];char line[1024];struct stat buf;if (pipe(fd) < 0) { /*創(chuàng)建管道*/printf("pipe error.\n");return;}fstat(fd[0], &buf);if (S_ISFIFO(buf.st_mode)) { /*S_ISFIFO為測試此文件類型是否為管道文件*/printf("fd[0]: FIFO file type.\n");}printf("fd[0]: inode=%d\n", buf.st_ino);fstat(fd[1], &buf);if (S_ISFIFO(buf.st_mode)) {printf("fd[1]: FIFO file type.\n");}printf("fd[1]: inode=%d\n", buf.st_ino);write(fd[1], "hello world.\n", 12);n = read(fd[0], line, 512 );write(STDOUT_FILENO, line, n);n = write(fd[0], "HELLO WORLD.\n", 12); /*0端只允許讀,1端才允許寫,這樣做是為了測試*/if (-1 == n)printf("\nwrite error\n") ;
}int main()
{look_into_pipe() ;
}test_pipe2.cpp:
//pipe管道的使用
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(){int pipe_fd[2];pid_t pid;char buf_r[100];char *p_wbuf;int r_num;memset(buf_r, 0, sizeof(buf_r));if (pipe(pipe_fd) < 0){ //創(chuàng)建管道perror("pipe create error\n");return -1;}if ((pid = fork()) == 0){//表示在子進(jìn)程中//關(guān)閉管道寫描述符,進(jìn)行管道讀操作printf("child pipe1=%d; pipe2=%d\n", pipe_fd[0], pipe_fd[1]) ;close(pipe_fd[1]);//管道描述符中讀取sleep(2);if ((r_num = read(pipe_fd[0], buf_r, 100)) > 0) {printf("%d numbers read from the pipe, data is %s\n", r_num, buf_r);}close(pipe_fd[0]);exit(0);} else if (pid > 0) {//表示在父進(jìn)程中,父進(jìn)程寫//關(guān)閉管道讀描述符,進(jìn)行管道寫操作printf("parent pipe1=%d; pipe2=%d\n", pipe_fd[0], pipe_fd[1]) ;close(pipe_fd[0]);if (write(pipe_fd[1], "Hello", 5) != -1)printf("parent write1 success!\n");if (write(pipe_fd[1], " Pipe", 5) != 1)printf("parent write2 success!\n");close(pipe_fd[1]);sleep(3);//waitpid()與wait()功能類似,都是用戶主進(jìn)程等待子進(jìn)程結(jié)束或中斷waitpid(pid, NULL, 0);exit(0);} else {perror("fork error");exit(-1);}return 0;
}test_pipe3.cpp:
//標(biāo)準(zhǔn)流管道的使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 1024int main()
{FILE *fp;char *cmd = "ps -ef";char buf[BUFSIZE];buf[BUFSIZE] = '\0';if ((fp=popen(cmd, "r")) == NULL)perror("popen");while ((fgets(buf, BUFSIZE, fp)) != NULL)printf("%s", buf);pclose(fp);exit(0);
}test_pipe4.cpp:
#命名管道FIFO的使用,創(chuàng)建命名管道并寫入數(shù)據(jù)
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define FIFO "/tmp/fifo"int main()
{char buffer[80];int fd;int n;int ret;char info[80];unlink(FIFO); /*若存在該管道文件,則進(jìn)行刪除*/ret = mkfifo(FIFO, 0600); /*0600表明只有該用戶進(jìn)程有讀寫權(quán)限*/if (ret) {perror("mkfifo error");return -1;}memset(info, 0x00, sizeof(info));strcpy(info, "happy new year!");fd = open(FIFO, O_WRONLY);n=write(fd, info, strlen(info));if (n < 0) {perror("write error") ;return -1 ;}close(fd);return 0 ;
}test_pipe5.cpp:
//命名管道FIFO的使用,從命名管道中讀取數(shù)據(jù)
//此測試用例,與test_pipe4一起使用
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define FIFO "/tmp/fifo"int main()
{char buffer[80];int fd;int n ;char info[80] ;fd= open(FIFO, O_RDONLY);n = read(fd, buffer, 80);if (n < 0) {perror("read error") ;return -1 ;}printf("buffer=%s\n", buffer);close(fd);return 0 ;
}
信號是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以看做是異步通知,通知接收信號的進(jìn)程有哪些事情發(fā)生了。信號同時又是一種軟件中斷,當(dāng)某進(jìn)程接收到信號時,會中止當(dāng)前程序的執(zhí)行,去處理信號的注冊函數(shù),然后回到斷點程序繼續(xù)往下執(zhí)行。
進(jìn)程能對每一個信號設(shè)置獨(dú)立的處理方式:它能忽略該信號,也能設(shè)置相應(yīng)的信號處理程序(稱為捕捉),或?qū)π盘柺裁匆膊蛔?#xff0c;信號發(fā)生的時候執(zhí)行系統(tǒng)的默認(rèn)動作。
所有的信號中,有兩個信號(SIGSTOP和SIGKILL)是特別的,它們既不能被捕捉,也不能被忽略,也不能被阻塞,這個特性確保了系統(tǒng)管理員在所有時候內(nèi)都能用暫停信號和殺死信號結(jié)束某個進(jìn)程。
不可靠信號是指信號值小于32的信號。可靠信號是指后來添加的新信號(信號值位于32及64之間)。信號的可靠與不可靠只與信號值有關(guān),與信號的發(fā)送及安裝函數(shù)無關(guān)。非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。
現(xiàn)在在Linux系統(tǒng)中,不可靠信號和可靠信號的區(qū)別在于前者不支持排隊,可能會造成信號的丟失,而后者不會。
內(nèi)核為進(jìn)程產(chǎn)生信號,來說明不同的事件,這些事件就是信號源。主要的信號源有:(1)、異常:進(jìn)程運(yùn)行過程中出現(xiàn)的異常;(2)、其它進(jìn)程:一個進(jìn)程可以向另外一個或一組進(jìn)程發(fā)送信號;(3)、終端中斷:Ctrl+C等;(4)、作業(yè)控制:前臺、后臺進(jìn)程的管理;(5)、分配額:CPU超時或文件大小突破限制;(6)、通知:通知進(jìn)程某事件發(fā)生,如I/O就緒等;(7)、報警:計時器到期。
信號的三種操作方式:忽略此信號、捕捉喜歡、執(zhí)行系統(tǒng)的默認(rèn)動作。
信號的5種默認(rèn)動作:異常終止(abort)、退出(exit)、忽略(ignore)、停止(stop)、繼續(xù)(continue)。
阻塞信號允許信號被發(fā)送給進(jìn)程,但不進(jìn)行處理,需要等到阻塞解除后再處理。而忽略信號是進(jìn)程根本不接收該信號,所有被忽略的信號都被簡單丟棄。
kill函數(shù)可以向有用戶權(quán)限的任何進(jìn)程發(fā)送信號,通常用kill函數(shù)來結(jié)束進(jìn)程。與kill函數(shù)不同的是,raise函數(shù)只向進(jìn)程自身發(fā)送信號。使用alarm函數(shù)可以設(shè)置一個時間值(鬧鐘時間),在將來的某個時刻該時間值超過時發(fā)送信號。pause函數(shù)使調(diào)用進(jìn)程掛起直至捕捉到一個信號。
信號是與一定的進(jìn)程相聯(lián)系的。也就是說,一個進(jìn)程可以決定在進(jìn)程中對哪些信號進(jìn)行什么樣的處理。
signal函數(shù),有兩個形參,分別代表需要處理的信號編號值和處理信號函數(shù)的指針。它主要是用于前32種非實時信號的處理,不支持信號的傳遞信息。
sigaction函數(shù)用來查詢和設(shè)置信號處理方式,它是用來替換早期的signal函數(shù)。
信號集用來描述一類信號的集合,Linux所支持的信號可以全部或部分的出現(xiàn)在信號集中。信號集操作函數(shù)最常用的地方就是用于信號屏蔽。
信號量與其他進(jìn)程間通信的方式不大相同,它主要提供對進(jìn)程間共享資源訪問控制機(jī)制,相當(dāng)于內(nèi)存中的標(biāo)志,進(jìn)程可以根據(jù)它判定是否能夠訪問某些共享資源,從而實現(xiàn)多個進(jìn)程對某些共享資源的互斥訪問;同時,進(jìn)程也可以修改該標(biāo)志。信號量除了用于訪問控制外,還可用于進(jìn)程同步。
test_signal1.cpp:
//信號的使用
#include <unistd.h>
#include <signal.h>
#include <stdio.h>typedef void (*signal_handler)(int);void signal_handler_fun(int signal_num) /*信號處理函數(shù)*/
{ printf("catch signal %d\n", signal_num);
}int main()
{int i;int time ;signal_handler p_signal = signal_handler_fun;signal(SIGALRM, p_signal); /*注冊SIGALRM信號處理方式*///alarm()用來設(shè)置信號SIGALRM在經(jīng)過參數(shù)seconds指定的秒數(shù)后傳送給目前的進(jìn)程alarm(3);for (i=1; i<5; i++) {printf("sleep %d ...\n", i);sleep(1);}alarm(3);sleep(2);time=alarm(0); /*取消SIGALRM信號,返回剩余秒數(shù)*/printf("time=%d\n", time);for (i=1; i<3; i++) {printf("sleep %d ...\n", i);sleep(1);}return 0 ;
}test_signal2.cpp:
//信號的使用:父進(jìn)程發(fā)信號給子進(jìn)程
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t pid;int status;pid = fork() ;if (0 == pid) {printf("Hi I am child process!\n");sleep(10);} else if (pid > 0) {printf("send signal to child process (%d) \n", pid);sleep(1);//kill函數(shù)是將信號發(fā)送給指定的pid進(jìn)程/*發(fā)送SIGABRT信號給子進(jìn)程,此信號引起接收進(jìn)程異常終止*/kill(pid ,SIGABRT);/*等待子進(jìn)程返回終止信息*/wait(&status);if(WIFSIGNALED(status))printf("child process receive signal %d\n", WTERMSIG(status));} else {perror("fork error") ;return -1 ;}return 0 ;
}test_signal3.cpp:
//信號的使用
//運(yùn)行時,需要按:Ctrl+C或Ctrl+\
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>/*自定義信號處理函數(shù)*/
void my_func(int sign_no)
{if (sign_no == SIGINT)printf("I have get SIGINT\n");else if (sign_no == SIGQUIT)printf("I have get SIGQUIT\n");
}int main()
{printf("Waiting for signal SIGINT or SIGQUIT \n ");/*發(fā)出相應(yīng)的信號,并跳轉(zhuǎn)到信號處理函數(shù)處*/signal(SIGINT, my_func);signal(SIGQUIT, my_func);pause();pause();exit(0);
}test_signal4.cpp:
//信號的使用:sigaction函數(shù)
//此測試程序有段錯誤
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void new_op(int, siginfo_t *, void *);int main(int argc, char** argv)
{struct sigaction act;int sig;sig = atoi(argv[1]);sigemptyset(&act.sa_mask);act.sa_flags = SA_SIGINFO;act.sa_sigaction = new_op;if (sigaction(sig, &act, NULL) < 0) {perror("install sigal error");return -1 ;}while(1) {sleep(2);printf("wait for the signal\n");}return 0 ;
}void new_op(int signum, siginfo_t *info, void *myact)
{printf("receive signal %d\n", signum);sleep(5);
}test_signal5.cpp:
//信號集函數(shù)的使用,需要Ctrl+C和Ctrl+\的參與
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>/*自定義的信號處理函數(shù)*/
#if 0
void my_funcnew(int signum, siginfo_t *info, void *myact);
#endifvoid my_func(int signum)
{printf("If you want to quit, please try SIGQUIT\n");
}int main()
{sigset_t set, pendset;struct sigaction action1, action2;/*設(shè)置信號處理方式*/sigemptyset(&action1.sa_mask);#if 0 /*信號新的安裝機(jī)制*/action1.sa_flags = SA_SIGINFO;action1.sa_sigaction = my_funcnew;
#endif/*信號舊的安裝機(jī)制*/action1.sa_flags = 0;action1.sa_handler = my_func;sigaction(SIGINT, &action1, NULL);/*初始化信號集為空*/if (sigemptyset(&set) < 0) {perror("sigemptyset");return -1 ;}/*將相應(yīng)的信號加入信號集*/if (sigaddset(&set, SIGQUIT) < 0) {perror("sigaddset");return -1 ;}if (sigaddset(&set, SIGINT) < 0) {perror("sigaddset");return -1 ;}/*設(shè)置信號集屏蔽字*/if (sigprocmask(SIG_BLOCK, &set, NULL) < 0) {perror("sigprocmask");return -1 ;} else {printf("blocked\n");}/*測試信號是否加入該信號集*/if (sigismember(&set, SIGINT)) {printf("SIGINT in set\n") ;}sleep(30);/*測試未決信號*/if (sigpending(&pendset) <0) {perror("get pending mask error");}if (sigismember(&pendset, SIGINT)) {printf("signal SIGINT is pending\n");}sleep(30) ;if (sigprocmask(SIG_UNBLOCK, &set,NULL) < 0) {perror("sigprocmask");return -1 ;} else {printf("unblock\n");}while(1) {sleep(1) ;}return 0 ;
}
共享內(nèi)存區(qū)域是被多個進(jìn)程共享的一部分物理內(nèi)存。如果多個進(jìn)程都把該內(nèi)存區(qū)域映射到自己的虛擬地址空間,則這些進(jìn)程就都可以直接訪問該共享內(nèi)存區(qū)域,從而可以通過該區(qū)域進(jìn)行通信。共享內(nèi)存是進(jìn)程間共享數(shù)據(jù)的一種最快的方法,一個進(jìn)程向共享內(nèi)存區(qū)域?qū)懭肓藬?shù)據(jù),共享這個內(nèi)存區(qū)域的所有進(jìn)程就可以立刻看到其中的內(nèi)容。這塊共享虛擬內(nèi)存的頁面,出現(xiàn)在每一個共享該頁面的進(jìn)程的頁表中。
注:以上內(nèi)容及測試代碼整理自網(wǎng)絡(luò)。
總結(jié)
以上是生活随笔為你收集整理的Linux进程编程基础介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux Socket基础介绍
- 下一篇: Git基础(常用命令)介绍