歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

Linux進程編程基礎介紹

Linux系統是一個多進程的系統,它的進程之間具有並行性、互不干擾等特點。也就是說,每個進程都是一個獨立的運行單位,擁有各自的權利和責任。其中,各個進程都運行在獨立的虛擬地址空間,因此,即使一個進程發生異常,它也不會影響到系統中的其他進程。Linux進程是一個具有獨立功能的程序關於某個數據集合的一次可以並發執行的運行活動,是處於活動狀態的計算機程序。進程作為構成系統的基本細胞,不僅是系統內部獨立運行的實體,而且是獨立競爭資源的基本實體。

Linux進程是一個程序的一次執行的過程,同時也是資源分配的最小單元。它和程序是有本質區別的,程序是靜態的,它是一些保存在磁盤上的指令的有序集合,沒有任何執行的概念;進程是一個動態的概念,它是程序執行的過程,包括了動態創建、調度和消亡的整個過程。它是程序執行和資源管理的最小單位。

進程是程序的執行過程,根據它的生命周期可以劃分成3種狀態:(1)、執行態:該進程正在運行,即進程正在占用CPU;(2)、就緒態:進程已經具備執行的一切條件,正在等待分配CPU的處理時間片;(3)、等待態(阻塞態):進程不能使用CPU,若等待事件發生(等待的資源分配到)則可將其喚醒。

導致進程終止的三種情況:正常終止、異常終止、外部干擾。

終止進程的主要操作過程如下;(1)、找到指定進程的PCB;(2)、終止該進程的運行;(3)、回收該進程所占用的全部資源;(4)、終止其所有子孫進程,回收它們所占用的全部資源;(5)、將被終止進程的PCB從原來隊列中摘走。

進程阻塞的過程如下:(1)、立即停止當前進程的執行;(2)、現行進程的CPU現場保存;(3)、現行狀態由“運行”改為“阻塞”;(4)、轉到進程調度程序。

Linux 系統的進程狀態模型的各種狀態:用戶狀態、內核狀態、內存中就緒、內存中睡眠、就緒且換出、睡眠且換出、被搶先、創建狀態、僵死狀態。

進程的上下文是由用戶級上下文、寄存器上下文以及系統級上下文組成。主要內容是該進程用戶空間內容、寄存器內容以及與該進程有關的內核數據結構。當系統收到一個中斷、執行系統調用或內核做上下文切換時,就會保存進程的上下文。一個進程是在它的上下文中運行的,若要調度進程,就要進行上下文切換。

在Linux系統中,用戶創建一個進程的唯一方法就是使用系統調用fork。

Linux系統調用exit,是進程用來終止執行時調用的。進程發出該調用,內核就會釋放該進程所占的資源,釋放進程上下文所占的內存空間,保留進程表項,將進程表項中紀錄進程狀態的字段設為僵死狀態。內核在進程收到不可捕捉的信號時,會從內核內部調用exit,使得進程退出。父進程通過wait得到其子進程的進程表項中記錄的計時數據,並釋放進程表項。最後,內核使得進程1(init 進程,init進程是系統所有進程的起點,它的進程號是1。)接收終止執行的進程的所有子進程。如果有子進程僵死,就向init進程發出一個SIGCHLD 的軟中斷信號。

一個進程通過調用wait來與它的子進程同步,如果發出調用的進程沒有子進程則返回一個錯誤,如果找到一個僵死的子進程就取子進程的PID及退出時提供給父進程的參數。如果有子進程,但沒有僵死的子進程,發出調用的進程就睡眠在一個可中斷的級別上,直到收到一個子進程僵死(SIGCLD)的信號或其他信號。進程控制的一個主要內容就是對其他程序引用。該功能是通過系統調用exec來實現的,該調用將一個可執行的程序文件讀入,代替發出調用的進程執行。內核讀入程序文件的正文,清除原先進程的數據區,清除原先用戶軟中斷信號處理函數的地址,當exec調用返回時,進程執行新的正文。

Linux系統是一個分時系統,內核給每個進程分一個時間片,該進程的時間片用完就會調度另一個進程執行。

進程調度分成兩個部分,一個是調度的時機,即什麼時候調度;一個是調度的算法,即如何調度和調度哪個進程。

fork函數啟動一個新的進程,這個進程幾乎是當前進程的一個拷貝:子進程和父進程使用相同的代碼段;子進程復制父進程的堆棧段和數據段。這樣,父進程的所有數據都可以留給子進程,但是,子進程一旦開始運行,雖然它繼承了父進程的一切數據,但實際上數據卻已經分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數據了。它們再要交互信息時,只有通過進程間通信來實現。

對於父進程,fork函數返回了子程序的進程號,而對於子程序,fork函數則返回零。在操作系統中,我們用ps函數就可以看到不同的進程號,對父進程而言,它的進程號是由比它更低層的系統調用賦予的,而對於子進程而言,它的進程號即是fork函數對父進程的返回值。

實際執行fork時,物理空間上兩個進程的數據段和堆棧段都還是共享著的,當有一個進程寫了某個數據時,這時兩個進程之間的數據才有了區別,系統就將有區別的"頁"從物理上也分開。系統在空間上的開銷就可以達到最小。

系統調用exec是用來執行一個可執行文件來代替當前進程的執行映像。需要注意的是,該調用並沒有生成新的進程,而是在原有進程的基礎上,替換原有進程的正文,調用前後是同一個進程,進程號PID不變。但執行的程序變了(執行的指令序列改變了)。

系統調用exec和fork經常結合使用,父進程fork一個子進程,在子進程中調用exec來替換子進程的執行映像,並發的執行一些操作。

系統調用exit的功能是終止發出調用的進程。

系統調用wait的功能是發出調用的進程只要有子進程,就睡眠直到它們中的一個終止為止。

函數調用sleep可以用來使進程掛起指定的秒數。

獲得進程相關的ID:與進程相關的ID有,(1)、真正用戶標識號(UID):該標識號負責標識運行進程的用戶;(2)、有效用戶標識號(EUID):該標識號負責標識以什麼用戶身份來給新創建的進程賦所有權、檢查文件的存取權限和檢查通過系統調用kill向進程發送軟中斷信號的許可權限;(3)、真正用戶組標識號(GID):負責標識運行進程的用戶所屬的組ID;(4)、有效用戶組標識號(EGID):用來標識目前進程所屬的用戶組。可能因為執行文件設置set-gid位而與gid不同;(5)、進程標識號(PID):用來標識進程;(6)、進程組標識號(process

group ID):一個進程可以屬於某個進程組。可以發送信號給一組進程。注意,它不同與gid。

如果要獲得進程的用戶標識號,用getuid調用。調用geteuid是用來獲得進程的有效用戶標識號。

如果要獲得運行進程的用戶組ID,使用getgid調用來獲得真正的用戶組ID,用getegid獲得有效的用戶組ID。

如果要獲得進程的ID,使用getpid調用;要獲得進程的父進程的ID,使用getppid調用。

如果要獲得進程所屬組的ID,使用getpgrp調用;若要獲得指定PID進程所屬組的ID用getpgid調用。

調用setuid為當前發出調用的進程設置真正和有效用戶ID。參數uid是新的用戶標識號(該標識號應該在/etc/passwd文件中存在)。調用setgid設置當前發出調用的進程的真正、有效用戶組ID。該調用允許進程指定進程的用戶組ID為參數gid,如果進程的有效用戶ID不是超級用戶,該參數gid必須等於真正用戶組ID、有效用戶組ID中的一個。

調用setpgrp用來將發出調用的進程的進程組ID設置成與該進程的PID相等。注意,以後由這個進程派生的子進程都擁有該進程組ID(除非修改子進程的進程組ID)。調用setpgid用來將進程號為參數pid的進程的進程組ID設定為參數pgid。如果參數pid為0,則修改發出調用進程的進程組ID。

chdir是用來將進程的當前工作目錄改為由參數指定的目錄。

系統調用chroot用來改變發出調用進程的根(“/”)目錄。

系統調用nice用來改變進程的優先權。

所謂僵屍進程,是指使用fork後,子進程先於父進程結束,但是因為父子進程間依然有關系,那麼子進程實際上不會真正意義上終結,如果查看當前進程表,會發現該進程依然存在。僵屍進程是非常特殊的一種,它已經放棄了幾乎所有內存空間,沒有任何可執行代碼,也不能被調度,僅僅在進程列表中保留一個位置,記載該進程的退出狀態等信息供其他進程收集,除此之外,僵屍進程不再占有任何內存空間。系統對一個用戶可以同時運行的進程數是有限制的,對超級用戶沒有該限制,但也不能超過進程表的最大表項的數目

Linux下一個進程在內存裡有三部的數據,就是"代碼段"、"堆棧段"和"數據段"。這三個部分也是構成一個完整的執行序列的必要的部分。"代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。而數據段則存放程序的全局變量,常數以及動態數據分配的數據空間(比如用malloc 之類的函數取得的空間)。系統如果同時運行數個相同的程序,它們之間就不能使用同一個堆棧段和數據段。test_fork1.cpp:

//fork函數的使用

//輸出結果的順序和進程調度的順序有關

#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) { /* 這裡是子進程執行的代碼 */

strcpy(buf, "This is child process write\n");

printf("This is child process\n");

printf("My PID(child) is %d\n", getpid()); /*打印出本進程的ID*/

printf("My parent PID is %d\n", getppid()); /*打印出父進程的ID*/

write(fd, buf, strlen(buf));

close(fd);

exit(0);

} else { /* 這裡是父進程執行的代碼 */

printf("This is parent process\n");

printf("My PID(parent) is %d\n",getpid());/*打印出本進程的ID*/

printf("My child PID is %d\n", cld_pid);/*打印出子進程的ID*/

write(fd, buf, strlen(buf));

close(fd);

}

//父子進程是彼此相互獨立運行的,所以要想讓父進程等待子進程,只需使用wait()系統調用。

wait(&status); /* 如果此處沒有這一句會如何?*/

return 0;

}

test_fork2.cpp:

//fork的使用

//屏幕上交替出現子進程與父進程各打印出的一千條信息

#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) {

/* 子進程程序 */

for (i = 1; i <1000; i ++)

printf("This is child process\n");

} else {

/* 父進程程序*/

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) {/* 如果是子進程 */

sleep(10);/* 睡眠10 秒 */

//exit(0);

return 0;

}

/* 如果是父進程 */

do {

pr = waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG 參數,waitpid 不會在這裡等待 */

if (pr == 0) {/* 如果沒有收集到子進程 */

printf("No child exited\n");

sleep(1);

}

} while (pr == 0); /* 沒有收集到子進程,就回去繼續嘗試 */

if (pr == pc)

printf("successfully get child %d\n", pr);

else

printf("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;

/*先創建子進程1*/

child1 = fork();

/*子進程1的出錯處理*/

if (child1 == -1) {

printf("Child1 fork error\n");

exit(1); /*異常退出*/

} else if(child1 == 0) { /*在子進程1中調用execlp()函數*/

printf("I am child1 and I execute 'ls -l'\n");

if (execlp("ls", "ls", "-l", NULL) < 0) {

printf("Child1 execlp error\n");

}

} else {/*在父進程中再創建進程2,然後等待兩個子進程的退出*/

child2 = fork();

/*子進程2的出錯處理*/

if (child2 == -1) {

printf("Child2 fork error\n");

exit(1);

} else if (child2 == 0) {/*在子進程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);

else

printf("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);

else

printf("Erroe occured!\n");

}

exit(0);

}

管道和信號是進程間通信的兩種機制。操作系統中的每一個管道有兩個文件描述符,一個文件描述符用來讀,另一個用來寫。信號是一個軟件中斷,主要用於進程間異步事件通知與進程控制。進程間的通信類型有6種:(1)、管道(pipe)和有名管道(FIFO);(2)、信號(signal);(3)、共享內存;(4)、消息隊列;(5)、信號量;(6)、套接字(socket)。

進程間通信目的有5種:(1)、數據傳輸:一個進程需要將它的數據發送給另一個進程;(2)、共享數據:多個進程想要共享數據,一個進程對共享數據進行修改後,別的進程可以立刻看到;(3)、通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程);(4)、資源共享:多個進程之間共享同樣的資源,為了做到這一點,需要內核提供鎖和同步機制;(5)、進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,並能夠及時知道它的狀態改變。

管道是Linux中最常用的進程間通信的IPC機制。使用管道時,一個進程的輸出可成為另外一個進程的輸入。當輸入/輸出的數據量特別大時,管道這種IPC機制非常有用。

在Linux中,通過將兩個file結構指向同一個臨時的VFS索引節點,而兩個VFS索引節點又指向同一個物理頁而實現管道。

管道允許在進程之間按先進先出的方式傳送數據,管道也能使進程同步執行。管道傳統的實現方法是通過文件系統作為存儲數據的地方。有兩種類型的管道:一種是無名管道,簡稱為管道;另一種是有名管道,也稱為FIFO。進程使用系統調用open來打開有名管道,使用系統調用pipe來建立無名管道。使用無名管道通訊的進程,必須是發出pipe調用的進程及其子進程。使用有名管道通訊的進程沒有上述限制。

系統調用dup是用來復制一個文件描述符,也就是將進程u區的文件描述符表中的一項復制一份,使得這兩項同時指向系統文件表的同一表項。

pipe管道:若要創建一個簡單的管道,可以使用系統調用pipe(),它接收一個參數,也就是一個包括兩個整數的數組。如果系統調用成功,此數組將包括管道使用的兩個文件描述符,一個為讀端,一個為寫端。pipe管道是半雙工的,數據只能向一個方向流動;需要雙方通信時,需要建立起兩個管道;只能用於父子進程或者兄弟進程之間。管道主要用於父子進程間通信。實際上,通常先創建一個管道,再通過fork函數創建一個子進程,其實pipe的兩個文件描述符指向相同的內存空間,只不過filedes[1]有寫權限,filedes[0]有讀權限。由於進程的文件描述符0指向標准輸入(鍵盤),1指向標准輸出(屏幕),2指向標准錯誤(屏幕),所以進程可用的文件描述符從3開始。進程是通過對文件描述符的操作從而實現對文件描述符所指向文件的操作。

標准流管道:管道的操作也支持文件流模式,這種管道稱為標准流管道。標准流管道通過popen()創建一個管道popen()會調用fork()產生一個子進程,執行一個shell以運行命令來開啟一個進程,並把執行結果寫入管道中,然後返回一個文件指針。程序通過文件指針可讀取管道中的內容。使用popen()創建的標准流管道,需要用pclose()進行關閉。命名管道(FIFO):和一般的管道基本相同,但也有一些顯著的不同,(1)、命名管道是在文件系統中作為一個特殊的設備文件而存在的;(2)、不同祖先的進程之間可以通過命名管道共享數據;(3)、當共享命名管道的進程執行完所有的I/O操作以後,命名管道將繼續保存在文件系統中,以便以後使用;(4)、普通管道只能由父子兄弟等相關進程使用,它們共同的祖先進程創建了管道。但是,通過命名管道,不相關的進程也能交換數據;(5)、一旦已經用mkfifo函數創建了一個FIFO,就可用open打開它。實際上,一般的文件I/O函數(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) { /*創建管道*/

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){ //創建管道

perror("pipe create error\n");

return -1;

}

if ((pid = fork()) == 0){//表示在子進程中

//關閉管道寫描述符,進行管道讀操作

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) {//表示在父進程中,父進程寫

//關閉管道讀描述符,進行管道寫操作

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()功能類似,都是用戶主進程等待子進程結束或中斷

waitpid(pid, NULL, 0);

exit(0);

} else {

perror("fork error");

exit(-1);

}

return 0;

}

test_pipe3.cpp:

//標准流管道的使用

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <fcntl.h>

#define BUFSIZE 1024

int 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的使用,創建命名管道並寫入數據

#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); /*若存在該管道文件,則進行刪除*/

ret = mkfifo(FIFO, 0600); /*0600表明只有該用戶進程有讀寫權限*/

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的使用,從命名管道中讀取數據

//此測試用例,與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 ;

}

信號是進程間通信機制中唯一的異步通信機制,可以看做是異步通知,通知接收信號的進程有哪些事情發生了。信號同時又是一種軟件中斷,當某進程接收到信號時,會中止當前程序的執行,去處理信號的注冊函數,然後回到斷點程序繼續往下執行。進程能對每一個信號設置獨立的處理方式:它能忽略該信號,也能設置相應的信號處理程序(稱為捕捉),或對信號什麼也不做,信號發生的時候執行系統的默認動作。

所有的信號中,有兩個信號(SIGSTOP和SIGKILL)是特別的,它們既不能被捕捉,也不能被忽略,也不能被阻塞,這個特性確保了系統管理員在所有時候內都能用暫停信號和殺死信號結束某個進程。

不可靠信號是指信號值小於32的信號。可靠信號是指後來添加的新信號(信號值位於32及64之間)。信號的可靠與不可靠只與信號值有關,與信號的發送及安裝函數無關。非實時信號都不支持排隊,都是不可靠信號;實時信號都支持排隊,都是可靠信號。現在在Linux系統中,不可靠信號和可靠信號的區別在於前者不支持排隊,可能會造成信號的丟失,而後者不會。

內核為進程產生信號,來說明不同的事件,這些事件就是信號源。主要的信號源有:(1)、異常:進程運行過程中出現的異常;(2)、其它進程:一個進程可以向另外一個或一組進程發送信號;(3)、終端中斷:Ctrl+C等;(4)、作業控制:前台、後台進程的管理;(5)、分配額:CPU超時或文件大小突破限制;(6)、通知:通知進程某事件發生,如I/O就緒等;(7)、報警:計時器到期。信號的三種操作方式:忽略此信號、捕捉喜歡、執行系統的默認動作。

信號的5種默認動作:異常終止(abort)、退出(exit)、忽略(ignore)、停止(stop)、繼續(continue)。

阻塞信號允許信號被發送給進程,但不進行處理,需要等到阻塞解除後再處理。而忽略信號是進程根本不接收該信號,所有被忽略的信號都被簡單丟棄。

kill函數可以向有用戶權限的任何進程發送信號,通常用kill函數來結束進程。與kill函數不同的是,raise函數只向進程自身發送信號。使用alarm函數可以設置一個時間值(鬧鐘時間),在將來的某個時刻該時間值超過時發送信號。pause函數使調用進程掛起直至捕捉到一個信號。

信號是與一定的進程相聯系的。也就是說,一個進程可以決定在進程中對哪些信號進行什麼樣的處理。

signal函數,有兩個形參,分別代表需要處理的信號編號值和處理信號函數的指針。它主要是用於前32種非實時信號的處理,不支持信號的傳遞信息。

sigaction函數用來查詢和設置信號處理方式,它是用來替換早期的signal函數。

信號集用來描述一類信號的集合,Linux所支持的信號可以全部或部分的出現在信號集中。信號集操作函數最常用的地方就是用於信號屏蔽。

信號量與其他進程間通信的方式不大相同,它主要提供對進程間共享資源訪問控制機制,相當於內存中的標志,進程可以根據它判定是否能夠訪問某些共享資源,從而實現多個進程對某些共享資源的互斥訪問;同時,進程也可以修改該標志。信號量除了用於訪問控制外,還可用於進程同步。

test_signal1.cpp:

//信號的使用

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

typedef void (*signal_handler)(int);

void signal_handler_fun(int signal_num) /*信號處理函數*/

{

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()用來設置信號SIGALRM在經過參數seconds指定的秒數後傳送給目前的進程

alarm(3);

for (i=1; i<5; i++) {

printf("sleep %d ...\n", i);

sleep(1);

}

alarm(3);

sleep(2);

time=alarm(0); /*取消SIGALRM信號,返回剩余秒數*/

printf("time=%d\n", time);

for (i=1; i<3; i++) {

printf("sleep %d ...\n", i);

sleep(1);

}

return 0 ;

}

test_signal2.cpp:

//信號的使用:父進程發信號給子進程

#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函數是將信號發送給指定的pid進程

/*發送SIGABRT信號給子進程,此信號引起接收進程異常終止*/

kill(pid ,SIGABRT);

/*等待子進程返回終止信息*/

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:

//信號的使用

//運行時,需要按:Ctrl+C或Ctrl+\

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include<unistd.h>

/*自定義信號處理函數*/

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 ");

/*發出相應的信號,並跳轉到信號處理函數處*/

signal(SIGINT, my_func);

signal(SIGQUIT, my_func);

pause();

pause();

exit(0);

}

test_signal4.cpp:

//信號的使用:sigaction函數

//此測試程序有段錯誤

#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:

//信號集函數的使用,需要Ctrl+C和Ctrl+\的參與

#include <sys/types.h>

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

/*自定義的信號處理函數*/

#if 0

void my_funcnew(int signum, siginfo_t *info, void *myact);

#endif

void my_func(int signum)

{

printf("If you want to quit, please try SIGQUIT\n");

}

int main()

{

sigset_t set, pendset;

struct sigaction action1, action2;

/*設置信號處理方式*/

sigemptyset(&action1.sa_mask);

#if 0 /*信號新的安裝機制*/

action1.sa_flags = SA_SIGINFO;

action1.sa_sigaction = my_funcnew;

#endif

/*信號舊的安裝機制*/

action1.sa_flags = 0;

action1.sa_handler = my_func;

sigaction(SIGINT, &action1, NULL);

/*初始化信號集為空*/

if (sigemptyset(&set) < 0) {

perror("sigemptyset");

return -1 ;

}

/*將相應的信號加入信號集*/

if (sigaddset(&set, SIGQUIT) < 0) {

perror("sigaddset");

return -1 ;

}

if (sigaddset(&set, SIGINT) < 0) {

perror("sigaddset");

return -1 ;

}

/*設置信號集屏蔽字*/

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 ;

}

共享內存區域是被多個進程共享的一部分物理內存。如果多個進程都把該內存區域映射到自己的虛擬地址空間,則這些進程就都可以直接訪問該共享內存區域,從而可以通過該區域進行通信。共享內存是進程間共享數據的一種最快的方法,一個進程向共享內存區域寫入了數據,共享這個內存區域的所有進程就可以立刻看到其中的內容。這塊共享虛擬內存的頁面,出現在每一個共享該頁面的進程的頁表中。注:以上內容及測試代碼整理自網絡。

Copyright © Linux教程網 All Rights Reserved