日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux高并发服务器开发---笔记2(多进程)

發(fā)布時(shí)間:2023/12/8 linux 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux高并发服务器开发---笔记2(多进程) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

0630

第4章 項(xiàng)目制作與技能提升

  • 4.0 視頻課鏈接
  • 4.1 項(xiàng)目介紹與環(huán)境搭建
  • 4.2 Linux系統(tǒng)編程1、4.3 Linux系統(tǒng)編程2
  • 4.4 多進(jìn)程
    • 1-9
    • 10.進(jìn)程間通信☆☆☆
      • 進(jìn)程間通信的概念(IPC)
      • Linux 進(jìn)程間通信的方式(七種)
      • ①匿名管道(管道)--- 親緣關(guān)系的進(jìn)程
        • 查看管道緩沖大小命令:
        • 查看管道緩沖大小函數(shù):fpathconf()函數(shù)
        • 示例:
      • ①有名管道(命名管道,FIFO)
      • 補(bǔ)充:管道的讀寫特點(diǎn)
        • 示例:(將管道設(shè)置為非阻塞)--- fcntl()函數(shù)☆☆☆
      • ②內(nèi)存映射 --- mmap()函數(shù)
        • 示例1(匿名映射)--- 只能用在有親緣關(guān)系的進(jìn)程通信
        • 示例2(有名映射)
      • ③共享內(nèi)存
        • 使用步驟:
        • 共享內(nèi)存相關(guān)的函數(shù):
        • 問題1:操作系統(tǒng)如何知道一塊共享內(nèi)存被多少個(gè)進(jìn)程關(guān)聯(lián)?
        • 問題2:可不可以對共享內(nèi)存進(jìn)行多次刪除 `shmctl`
        • ☆☆☆共享內(nèi)存和內(nèi)存映射的區(qū)別
        • 共享內(nèi)存操作命令(打印當(dāng)前系統(tǒng)中所有的 進(jìn)程間通信方式 的信息)
        • 示例
      • ④信號signal
        • 信號的基本概念
        • 信號相關(guān)的函數(shù)1:kill()、raise()、abort()、alarm()、setitimer()
          • ①kill()函數(shù):給任何的進(jìn)程發(fā)送任何的信號 sig
          • ②raise()函數(shù):給當(dāng)前進(jìn)程發(fā)送信號
          • ③abort()函數(shù):殺死當(dāng)前進(jìn)程
          • ☆☆☆④alarm()函數(shù):設(shè)置定時(shí)器(鬧鐘),定時(shí)時(shí)間到了就終止當(dāng)前的進(jìn)程
            • 示例1:設(shè)置一個(gè)定時(shí)器
            • 示例2:電腦1秒鐘能數(shù)多少個(gè)數(shù)?
          • ⑤setitimer()函數(shù):(既可以用來延時(shí)執(zhí)行,也可定時(shí)執(zhí)行;可以實(shí)現(xiàn)周期性定時(shí))
        • 信號相關(guān)的函數(shù)2:(信號捕捉函數(shù))signal()函數(shù)、sigaction()函數(shù)
        • 信號集(信號的阻塞 --- 防止信號打斷敏感的操作)
          • 信號集相關(guān)操作函數(shù)
        • 內(nèi)核實(shí)現(xiàn)信號捕捉的過程
        • 補(bǔ)充:SIGCHLD信號(解決僵尸進(jìn)程的問題)
  • 4.5 多線程

4.0 視頻課鏈接

4.1 項(xiàng)目介紹與環(huán)境搭建

4.2 Linux系統(tǒng)編程1、4.3 Linux系統(tǒng)編程2

4.4 多進(jìn)程

1-9

Linux高并發(fā)服務(wù)器開發(fā)—筆記1

10.進(jìn)程間通信☆☆☆

(視頻課從01:30:30開始)

進(jìn)程間通信的概念(IPC)

進(jìn)程是一個(gè)獨(dú)立的資源分配單元,不同進(jìn)程(這里所說的進(jìn)程通常指的是用戶進(jìn)程)之間的資源是獨(dú)立的,沒有關(guān)聯(lián),不能在一個(gè)進(jìn)程中直接訪問另一個(gè)進(jìn)程的資源

但是,進(jìn)程不是孤立的,不同的進(jìn)程需要進(jìn)行信息的交互和狀態(tài)的傳遞等,因此需要進(jìn)程間通信( IPC:Inter Processes Communication )。

進(jìn)程間通信的目的:

  • 數(shù)據(jù)傳輸:一個(gè)進(jìn)程需要將它的數(shù)據(jù)發(fā)送給另一個(gè)進(jìn)程。
  • 通知事件:一個(gè)進(jìn)程需要向另一個(gè)或一組進(jìn)程發(fā)送消息,通知它(它們)發(fā)生了某種事件(如進(jìn)程終止時(shí)要通知父進(jìn)程)。
  • 資源共享:多個(gè)進(jìn)程之間共享同樣的資源。為了做到這一點(diǎn),需要內(nèi)核提供互斥和同步機(jī)制。
  • 進(jìn)程控制:有些進(jìn)程希望完全控制另一個(gè)進(jìn)程的執(zhí)行(如 Debug 進(jìn)程),此時(shí)控制進(jìn)程希望能夠攔截另一個(gè)進(jìn)程的所有陷入和異常,并能夠及時(shí)知道它的狀態(tài)改變。

Linux 進(jìn)程間通信的方式(七種)

管道(匿名管道、有名管道)
信號量(互斥鎖)
共享內(nèi)存
內(nèi)存映射(匿名映射、內(nèi)存映射)
消息隊(duì)列
信號
socket

①匿名管道(管道)— 親緣關(guān)系的進(jìn)程

管道也叫無名(匿名)管道,它是 UNIX 系統(tǒng) IPC(進(jìn)程間通信)的最古老形式,所有的 UNIX 系統(tǒng)都支持這種通信機(jī)制。
統(tǒng)計(jì)一個(gè)目錄中文件的數(shù)目命令:ls | wc –l,為了執(zhí)行該命令,shell 創(chuàng)建了兩個(gè)進(jìn)程來分別執(zhí)行 ls 和 wc。

管道其實(shí)是一個(gè)在內(nèi)核內(nèi)存中維護(hù)的緩沖器,這個(gè)緩沖器的存儲能力是有限的,不同的操作系統(tǒng)大小不一定相同。
管道擁有文件的特質(zhì):讀操作、寫操作匿名管道沒有文件實(shí)體,有名管道有文件實(shí)體,但不存儲數(shù)據(jù)。可以按照操作文件的方式對管道進(jìn)行操作。

一個(gè)管道是一個(gè)字節(jié)流,使用管道時(shí)不存在消息或者消息邊界的概念,從管道讀取數(shù)據(jù)的進(jìn)程可以讀取任意大小的數(shù)據(jù)塊,而不管寫入進(jìn)程寫入管道的數(shù)據(jù)塊的大小是多少。

通過管道傳遞的數(shù)據(jù)是順序的,從管道中讀取出來的字節(jié)的順序和它們被寫入管道的順序是完全一樣的。

在管道中的數(shù)據(jù)的傳遞方向是單向的,一端用于寫入,一端用于讀取,管道是半雙工的

從管道讀數(shù)據(jù)是一次性操作,數(shù)據(jù)一旦被讀走,它就從管道中被拋棄,釋放空間以便寫更多的數(shù)據(jù),在管道中無法使用 lseek() 來隨機(jī)的訪問數(shù)據(jù)。

匿名管道只能在具有公共祖先(父進(jìn)程與子進(jìn)程,或者兩個(gè)兄弟進(jìn)程,具有親緣關(guān)系的進(jìn)程之間使用

創(chuàng)建匿名管道:

#include <unistd.h> int pipe(int pipefd[2]);

功能:創(chuàng)建一個(gè)匿名管道,用來進(jìn)程間通信。
參數(shù):int pipefd[2] 這個(gè)數(shù)組是一個(gè)傳出參數(shù)。

  • pipefd[0] 對應(yīng)的是管道的讀端
  • pipefd[1] 對應(yīng)的是管道的寫端

返回值: 成功 0;失敗 -1

管道默認(rèn)是阻塞的:如果管道中沒有數(shù)據(jù),read阻塞;如果管道滿了,write阻塞

注意:匿名管道只能用于具有關(guān)系的進(jìn)程之間的通信(父子進(jìn)程,兄弟進(jìn)程)

示例:

// 子進(jìn)程發(fā)送數(shù)據(jù)給父進(jìn)程,父進(jìn)程讀取到數(shù)據(jù)輸出 #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h>int main() {// 在fork之前創(chuàng)建管道int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe");exit(0);}// 創(chuàng)建子進(jìn)程pid_t pid = fork();if(pid > 0) {// 父進(jìn)程printf("i am parent process, pid : %d\n", getpid());// 關(guān)閉寫端close(pipefd[1]);// 從管道的讀取端讀取數(shù)據(jù)char buf[1024] = {0};while(1) {int len = read(pipefd[0], buf, sizeof(buf));printf("parent recv : %s, pid : %d\n", buf, getpid());// 向管道中寫入數(shù)據(jù)// const char * str = "hello,i am parent";// write(pipefd[1], str, strlen(str));// sleep(3);}} else if(pid == 0){// 子進(jìn)程printf("i am child process, pid : %d\n", getpid());// 關(guān)閉讀端close(pipefd[0]);char buf[1024] = {0};while(1) {// 向管道中寫入數(shù)據(jù)const char *str = "hello,i am child";write(pipefd[1], str, strlen(str));sleep(3);// int len = read(pipefd[0], buf, sizeof(buf));// printf("child recv : %s, pid : %d\n", buf, getpid());// bzero(buf, 1024);}}return 0; }

查看管道緩沖大小命令:

ulimit –a root@VM-16-2-ubuntu:~# ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 15343 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 15343 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited

其中pipe size (512 bytes, -p) 8表示管道大小為512B* 8 = 4KB。

查看管道緩沖大小函數(shù):fpathconf()函數(shù)

#include <unistd.h> long fpathconf(int fd, int name); //gets a value for the configuration option name for the open file descriptor fd.

示例:

#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h>int main() {int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe");exit(0);}// 獲取管道的大小long size = fpathconf(pipefd[0], _PC_PIPE_BUF);printf("pipe size : %ld\n", size);return 0; }

結(jié)果:

pipe size : 4096

4096字節(jié) = 4KB

示例:

// 子進(jìn)程發(fā)送數(shù)據(jù)給父進(jìn)程,父進(jìn)程讀取到數(shù)據(jù)輸出 #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h>int main() {// 在fork之前創(chuàng)建管道int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe");exit(0);}// 創(chuàng)建子進(jìn)程pid_t pid = fork();if(pid > 0) {// 父進(jìn)程printf("i am parent process, pid : %d\n", getpid());// 關(guān)閉寫端close(pipefd[1]);// 從管道的讀取端讀取數(shù)據(jù):char buf[1024] = {0};while(1) {int len = read(pipefd[0], buf, sizeof(buf));printf("parent recv : %s, pid : %d\n", buf, getpid());// 向管道中寫入數(shù)據(jù):// const char * str = "hello,i am parent";// write(pipefd[1], str, strlen(str));// sleep(3);}} else if(pid == 0){// 子進(jìn)程printf("i am child process, pid : %d\n", getpid());// 關(guān)閉讀端close(pipefd[0]);char buf[1024] = {0};while(1) {// 向管道中寫入數(shù)據(jù):const char *str = "hello,i am child";write(pipefd[1], str, strlen(str));sleep(3);// 從管道的讀取端讀取數(shù)據(jù):// int len = read(pipefd[0], buf, sizeof(buf));// printf("child recv : %s, pid : %d\n", buf, getpid());// bzero(buf, 1024);}}return 0; }

編譯運(yùn)行:

i am child process, pid : 2799345 i am parent process, pid : 2799340 parent recv : hello,i am child, pid : 2799340 parent recv : hello,i am child, pid : 2799340 parent recv : hello,i am child, pid : 2799340 parent recv : hello,i am child, pid : 2799340

①有名管道(命名管道,FIFO)

  • 匿名管道,由于沒有名字,只能用于親緣關(guān)系的進(jìn)程間通信。為了克服這個(gè)缺點(diǎn),提出了有名管道(FIFO),也叫命名管道、FIFO文件。
  • 有名管道(FIFO)不同于匿名管道之處在于它提供了一個(gè)路徑名與之關(guān)聯(lián),以 FIFO 的文件形式存在于文件系統(tǒng)中,并且其打開方式與打開一個(gè)普通文件是一樣的,這樣即使與 FIFO 的創(chuàng)建進(jìn)程不存在親緣關(guān)系的進(jìn)程,只要可以訪問該路徑,就能夠彼此通過 FIFO 相互通信,因此,通過 FIFO, 不相關(guān)的進(jìn)程也能交換數(shù)據(jù)
  • 一旦打開了 FIFO,就能在它上面使用與操作匿名管道和其他文件的系統(tǒng)調(diào)用一樣的I/O系統(tǒng)調(diào)用了(如read()、write()和close())。與管道一樣,FIFO 也有一個(gè)寫入端讀取端,并且從管道中讀取數(shù)據(jù)的順序與寫入的順序是一樣的。FIFO 的名稱也由此而來:先入先出
  • 有名管道(FIFO)和匿名管道(pipe)有一些特點(diǎn)是相同的,不一樣的地方在于:
    FIFO 在文件系統(tǒng)中作為一個(gè)特殊文件存在,但 FIFO 中的內(nèi)容卻存放在內(nèi)存中
    當(dāng)使用 FIFO 的進(jìn)程退出后,FIFO 文件將繼續(xù)保存在文件系統(tǒng)中以便以后使用;
    FIFO 有名字,不相關(guān)的進(jìn)程可以通過打開有名管道進(jìn)行通信
  • 通過命令創(chuàng)建有名管道:

    mkfifo 名字

    通過函數(shù)創(chuàng)建有名管道:

    #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);

    參數(shù):

    • pathname: 管道名稱的路徑
    • mode: 文件的權(quán)限, 和 open 的參數(shù) mode 是一樣的,是一個(gè)八進(jìn)制的數(shù)

    返回值:成功返回0,失敗返回-1,并設(shè)置錯(cuò)誤號

    一旦使用 mkfifo 創(chuàng)建了一個(gè) FIFO,就可以使用 open 打開它,常見的文件I/O 函數(shù)都可用于 fifo。如:close、read、write、unlink 等。
    FIFO 嚴(yán)格遵循先進(jìn)先出(First in First out),對管道及 FIFO 的讀總是從開始處返回?cái)?shù)據(jù),對它們的寫則把數(shù)據(jù)添加到末尾。它們不支持諸如 lseek() 等文件定位操作

    示例:

    mkfifo.c

    #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h>int main() {// 判斷文件是否存在int ret = access("fifo1", F_OK);if(ret == -1) {printf("管道不存在,創(chuàng)建管道\n"); ret = mkfifo("fifo1", 0664);//0664是權(quán)限if(ret == -1) {perror("mkfifo");exit(0);} }return 0; }

    read.c

    #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h>// 從管道中讀取數(shù)據(jù) int main() {// 1.打開管道文件int fd = open(" ", O_RDONLY);if(fd == -1) {perror("open");exit(0);}// 讀數(shù)據(jù)while(1) {char buf[1024] = {0};int len = read(fd, buf, sizeof(buf));if(len == 0) {printf("寫端斷開連接了...\n");break;}printf("recv buf : %s\n", buf);}close(fd);return 0; }

    write.c

    #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h>int main() {// 3.以只寫的方式打開管道int fd = open("fifo1", O_WRONLY);if(fd == -1) {perror("open");exit(0);}// 寫數(shù)據(jù)for(int i = 0; i < 100; i++) {char buf[1024];sprintf(buf, "hello, %d\n", i);printf("write data : %s\n", buf);write(fd, buf, strlen(buf));sleep(1);}close(fd);return 0; }

    分別生成對應(yīng)的以.o結(jié)尾的可執(zhí)行文件,然后在兩個(gè)終端分別運(yùn)行讀和寫:

    補(bǔ)充:

    有名管道的注意事項(xiàng):1.一個(gè)為只讀而打開一個(gè)管道的進(jìn)程會阻塞,直到另外一個(gè)進(jìn)程為只寫打開管道2.一個(gè)為只寫而打開一個(gè)管道的進(jìn)程會阻塞,直到另外一個(gè)進(jìn)程為只讀打開管道讀管道:管道中有數(shù)據(jù),`read`返回**實(shí)際讀到的字節(jié)數(shù)**管道中無數(shù)據(jù):管道寫端被全部關(guān)閉,read返回0,(相當(dāng)于讀到文件末尾)寫端沒有全部被關(guān)閉,read阻塞等待寫管道:管道讀端被全部關(guān)閉,進(jìn)行異常終止(收到一個(gè)SIGPIPE信號)管道讀端沒有全部關(guān)閉:管道已經(jīng)滿了,write會阻塞管道沒有滿,write將數(shù)據(jù)寫入,并返回實(shí)際寫入的字節(jié)數(shù)。

    補(bǔ)充:管道的讀寫特點(diǎn)

    管道的讀寫特點(diǎn):
    使用管道時(shí),需要注意以下幾種特殊的情況(假設(shè)都是阻塞I/O操作
    1.所有的指向管道寫端的文件描述符都關(guān)閉了(管道寫端引用計(jì)數(shù)為0),有進(jìn)程從管道的讀端讀數(shù)據(jù),那么管道中剩余的數(shù)據(jù)被讀取以后,再次read會返回0,就像讀到文件末尾一樣。

    2.如果有指向管道寫端的文件描述符沒有關(guān)閉(管道的寫端引用計(jì)數(shù)大于0),而持有管道寫端的進(jìn)程也沒有往管道中寫數(shù)據(jù),這個(gè)時(shí)候有進(jìn)程從管道中讀取數(shù)據(jù),那么管道中剩余的數(shù)據(jù)被讀取后,再次read會阻塞,直到管道中有數(shù)據(jù)可以讀了才讀取數(shù)據(jù)并返回。

    3.如果所有指向管道讀端的文件描述符都關(guān)閉了(管道的讀端引用計(jì)數(shù)為0),這個(gè)時(shí)候有進(jìn)程
    向管道中寫數(shù)據(jù),那么該進(jìn)程會收到一個(gè)信號SIGPIPE, 通常會導(dǎo)致進(jìn)程異常終止。

    4.如果有指向管道讀端的文件描述符沒有關(guān)閉(管道的讀端引用計(jì)數(shù)大于0),而持有管道讀端的進(jìn)程也沒有從管道中讀數(shù)據(jù),這時(shí)有進(jìn)程向管道中寫數(shù)據(jù),那么在管道被寫滿的時(shí)候再次write會阻塞,直到管道中有空位置才能再次寫入數(shù)據(jù)并返回。

    總結(jié):
    讀管道:

    • 管道中有數(shù)據(jù),read返回實(shí)際讀到的字節(jié)數(shù)
    • 管道中無數(shù)據(jù):
      寫端被全部關(guān)閉,read返回0(相當(dāng)于讀到文件的末尾);
      寫端沒有完全關(guān)閉,read阻塞等待。

    寫管道:

    • 管道讀端全部被關(guān)閉,進(jìn)程異常終止(進(jìn)程收到SIGPIPE信號)。
    • 管道讀端沒有全部關(guān)閉:
      管道已滿,write阻塞
      管道沒有滿,write將數(shù)據(jù)寫入,并返回實(shí)際寫入的字節(jié)數(shù)

    示例:(將管道設(shè)置為非阻塞)— fcntl()函數(shù)☆☆☆

    #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> /*設(shè)置管道非阻塞int flags = fcntl(fd[0], F_GETFL); // 獲取原來的flagflags |= O_NONBLOCK; // 修改flag的值fcntl(fd[0], F_SETFL, flags); // 設(shè)置新的flag */ int main() {// 在fork之前創(chuàng)建管道int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe");exit(0);}// 創(chuàng)建子進(jìn)程pid_t pid = fork();if(pid > 0) {// 父進(jìn)程printf("i am parent process, pid : %d\n", getpid());// 關(guān)閉寫端close(pipefd[1]);// 從管道的讀取端讀取數(shù)據(jù)char buf[1024] = {0};int flags = fcntl(pipefd[0], F_GETFL); // 獲取原來的flagflags |= O_NONBLOCK; // 修改flag的值fcntl(pipefd[0], F_SETFL, flags); // 設(shè)置新的flagwhile(1) {int len = read(pipefd[0], buf, sizeof(buf));printf("len : %d\n", len);printf("parent recv : %s, pid : %d\n", buf, getpid());memset(buf, 0, 1024);sleep(1);}} else if(pid == 0){// 子進(jìn)程printf("i am child process, pid : %d\n", getpid());// 關(guān)閉讀端close(pipefd[0]);char buf[1024] = {0};while(1) {// 向管道中寫入數(shù)據(jù)char * str = "hello,i am child";write(pipefd[1], str, strlen(str));sleep(5);} }return 0; }

    結(jié)果:

    ②內(nèi)存映射 — mmap()函數(shù)

    可以看①Linux簡明系統(tǒng)編程(嵌入式公眾號的課)—總課時(shí)12h中的mmap函數(shù)(用在信號量中):
    在當(dāng)前進(jìn)程的虛擬地址空間中創(chuàng)建一個(gè)新的映射
    如果成功創(chuàng)建了共享映射,就返回此映射區(qū)域的指針(地址)void* ;)

    內(nèi)存映射(Memory-mapped I/O)是將磁盤文件的數(shù)據(jù)映射到內(nèi)存,用戶通過修改內(nèi)存就能修改磁盤文件

    修改內(nèi)存映射區(qū)中的內(nèi)容,內(nèi)存映射會將修改后的內(nèi)容同步到磁盤文件;這樣如果有多個(gè)進(jìn)程映射的是同一個(gè)磁盤文件,這樣就可以通過以這個(gè)磁盤文件為中介實(shí)現(xiàn)進(jìn)程間的通信(每個(gè)進(jìn)程對映射到自己虛擬地址空間中的內(nèi)存映射區(qū)進(jìn)行操作即可),有點(diǎn)類似于有名管道中通過mkfifo創(chuàng)建的那個(gè)文件。

    使用內(nèi)存映射實(shí)現(xiàn)進(jìn)程間通信
    1.有關(guān)系的進(jìn)程(父子進(jìn)程)間通信
    還沒有子進(jìn)程的時(shí)候,通過唯一的父進(jìn)程,先創(chuàng)建內(nèi)存映射區(qū);
    有了內(nèi)存映射區(qū)以后,創(chuàng)建子進(jìn)程;
    父子進(jìn)程
    共享
    創(chuàng)建的內(nèi)存映射區(qū)

    2.沒有關(guān)系的進(jìn)程間通信
    準(zhǔn)備一個(gè)大小不是0的磁盤文件
    進(jìn)程1 通過磁盤文件創(chuàng)建內(nèi)存映射區(qū),得到一個(gè)操作這塊內(nèi)存的指針;
    進(jìn)程2 通過磁盤文件創(chuàng)建內(nèi)存映射區(qū),得到一個(gè)操作這塊內(nèi)存的指針;
    使用內(nèi)存映射區(qū)通信;

    注意:內(nèi)存映射區(qū)通信,是非阻塞

    ①mmap()函數(shù):

    #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

    功能:將一個(gè)文件或者設(shè)備的數(shù)據(jù)映射到內(nèi)存中
    參數(shù):

    • void *addr:寫 NULL, 系統(tǒng)會自動(dòng)分配一個(gè)空間來放這個(gè)共享映射
    • length : 要映射的數(shù)據(jù)的長度,這個(gè)值不能為0。建議使用文件的長度
      獲取文件的長度(文件大小):stat() 或者 lseek()
    • prot : 對申請的內(nèi)存映射區(qū)的操作權(quán)限
      -PROT_EXEC :可執(zhí)行的權(quán)限
      -PROT_READ :讀權(quán)限
      -PROT_WRITE :寫權(quán)限
      -PROT_NONE :沒有權(quán)限
      要操作映射內(nèi)存,必須要有讀的權(quán)限
      PROT_READ、PROT_READ|PROT_WRITE
    • flags :
      - MAP_SHARED : 映射區(qū)的數(shù)據(jù)會自動(dòng)和磁盤文件進(jìn)行同步,進(jìn)程間通信,必須要設(shè)置這個(gè)選項(xiàng)
      - MAP_PRIVATE :不同步,內(nèi)存映射區(qū)的數(shù)據(jù)改變了,對原來的文件不會修改,會重新創(chuàng)建一個(gè)新的文件。(copy on write)
    • fd: 需要映射的那個(gè)文件的文件描述符
      - 通過open得到,open的是一個(gè)磁盤文件
      - 注意:文件的大小不能為0,open指定的權(quán)限不能和prot參數(shù)有沖突。
      prot: PROT_READ open:只讀/讀寫
      prot: PROT_READ | PROT_WRITE open:讀寫
    • offset:偏移量,一般不用(即寫0)。如果要用就必須指定的是4k的整數(shù)倍,0表示不偏移。

    返回值:返回創(chuàng)建的內(nèi)存的首地址;失敗返回MAP_FAILED,(void *) -1

    ②munmap()函數(shù):

    #include <sys/mman.h> int munmap(void *addr, size_t length);

    功能:釋放內(nèi)存映射
    參數(shù):

    • addr : 要釋放的內(nèi)存的首地址
    • length : 要釋放的內(nèi)存的大小,要和mmap函數(shù)中的length參數(shù)的值一樣。

    示例1(匿名映射)— 只能用在有親緣關(guān)系的進(jìn)程通信

    匿名映射:不需要文件實(shí)體進(jìn)程一個(gè)內(nèi)存映射

    #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/wait.h>int main() {// 1.創(chuàng)建匿名內(nèi)存映射區(qū)int len = 4096;void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);//如果是匿名映射MAP_ANONYMOUS,那么倒數(shù)第二個(gè)參數(shù)fd就寫-1if(ptr == MAP_FAILED) {perror("mmap");exit(0);}// 父子進(jìn)程間通信pid_t pid = fork();if(pid > 0) {// 父進(jìn)程strcpy((char *) ptr, "hello, world");wait(NULL);}else if(pid == 0) {// 子進(jìn)程sleep(1);printf("%s\n", (char *)ptr);}// 釋放內(nèi)存映射區(qū)int ret = munmap(ptr, len);if(ret == -1) {perror("munmap");exit(0);}return 0; }

    示例2(有名映射)

    (共同映射到一個(gè)文件,如果這個(gè)文件不存在,就會返回錯(cuò)誤mmap: Bad file descriptor)

    #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <wait.h>int main() {// 1.打開一個(gè)文件int fd = open("test.txt", O_RDWR);int size = lseek(fd, 0, SEEK_END); // 獲取文件的大小// 2.創(chuàng)建內(nèi)存映射區(qū)void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(ptr == MAP_FAILED) {perror("mmap");exit(0);}// 3.創(chuàng)建子進(jìn)程pid_t pid = fork();if(pid > 0) {wait(NULL);// 父進(jìn)程:讀數(shù)據(jù)char buf[64];strcpy(buf, (char *)ptr);printf("read data : %s\n", buf);}else if(pid == 0){// 子進(jìn)程:寫數(shù)據(jù)strcpy((char *)ptr, "nihao a, son!!!");}// 關(guān)閉內(nèi)存映射區(qū)munmap(ptr, size);return 0; }

    ③共享內(nèi)存

    共享內(nèi)存允許兩個(gè)或者多個(gè)進(jìn)程共享物理內(nèi)存的同一塊區(qū)域(通常被稱為)。由于一個(gè)共享內(nèi)存段會成為一個(gè)進(jìn)程用戶空間的一部分,因此這種 IPC 機(jī)制無需內(nèi)核介入。所有需要做的就是讓一個(gè)進(jìn)程將數(shù)據(jù)復(fù)制進(jìn)共享內(nèi)存中,并且這部分?jǐn)?shù)據(jù)會對其他所有共享同一個(gè)段的進(jìn)程可用。

    與管道等要求發(fā)送進(jìn)程 將數(shù)據(jù)從用戶空間的緩沖區(qū)復(fù)制進(jìn)內(nèi)核內(nèi)存接收進(jìn)程將數(shù)據(jù)從內(nèi)核內(nèi)存復(fù)制進(jìn)用戶空間的緩沖區(qū) 的做法相比,這種 IPC 技術(shù)的速度更快。

    使用步驟:

    • 1.調(diào)用 shmget() 創(chuàng)建一個(gè)新共享內(nèi)存段或取得一個(gè)既有共享內(nèi)存段的標(biāo)識符(即由其他進(jìn)程創(chuàng)建的共享內(nèi)存段)。這個(gè)調(diào)用將返回后續(xù)調(diào)用中需要用到的共享內(nèi)存標(biāo)識符
    • 2.使用 shmat() 來附上(連接attach)共享內(nèi)存段,即使該段成為調(diào)用進(jìn)程的虛擬內(nèi)存的一部分
    • 3.此刻在程序中可以像對待其他可用內(nèi)存那樣對待這個(gè)共享內(nèi)存段。為引用這塊共享內(nèi)存,程序需要使用由 shmat()調(diào)用返回的 addr 值,它是一個(gè)指向進(jìn)程的虛擬地址空間中該共享內(nèi)存段的起點(diǎn)的指針。
    • 4.調(diào)用 shmdt() 來分離detach共享內(nèi)存段。在這個(gè)調(diào)用之后,進(jìn)程就無法再引用這塊共享內(nèi)存了。這一步是可選的,并且在進(jìn)程終止時(shí)會自動(dòng)完成這一步
    • 5.調(diào)用 shmctl() 來刪除control共享內(nèi)存段。只有當(dāng)當(dāng)前所有附加內(nèi)存段的進(jìn)程都與之分離后內(nèi)存段才會銷毀。只有一個(gè)進(jìn)程需要執(zhí)行這一步。

    共享內(nèi)存相關(guān)的函數(shù):

    頭文件:

    #include <sys/ipc.h> #include <sys/shm.h>

    ①shmget()函數(shù)

    int shmget(key_t key, size_t size, int shmflg);

    功能:創(chuàng)建一個(gè)新的共享內(nèi)存段,或者獲取一個(gè)已有的共享內(nèi)存段的標(biāo)識。新創(chuàng)建的內(nèi)存段中的數(shù)據(jù)都會被初始化為0
    參數(shù):

    • key : key_t類型是一個(gè)整形,通過這個(gè)找到或者創(chuàng)建一個(gè)共享內(nèi)存
      一般使用16進(jìn)制表示,非0值
    • size: 共享內(nèi)存的大小
    • shmflg: 屬性
      訪問權(quán)限
      附加屬性:創(chuàng)建/判斷共享內(nèi)存是不是存在
      - 創(chuàng)建:IPC_CREAT
      - 判斷共享內(nèi)存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
      IPC_CREAT | IPC_EXCL | 0664

    返回值:

    • 失敗:-1 并設(shè)置錯(cuò)誤號
    • 成功:>0 返回共享內(nèi)存的引用的ID,后面操作共享內(nèi)存都是通過這個(gè)值。

    ②shmat()函數(shù)

    void *shmat(int shmid, const void *shmaddr, int shmflg);

    功能:和當(dāng)前的進(jìn)程進(jìn)行關(guān)聯(lián)
    參數(shù):

    • shmid : 共享內(nèi)存的標(biāo)識(ID),由shmget返回值獲取
    • shmaddr: 申請的共享內(nèi)存的起始地址,指定NULL,內(nèi)核指定
    • shmflg : 對共享內(nèi)存的操作
      - 讀 : SHM_RDONLY, 必須要有讀權(quán)限
      - 讀寫: 0

    返回值:
    成功:返回共享內(nèi)存的首(起始)地址。 失敗(void *) -1

    ③shmdt()函數(shù)

    int shmdt(const void *shmaddr);

    功能:解除當(dāng)前進(jìn)程和共享內(nèi)存的關(guān)聯(lián)
    參數(shù):shmaddr,共享內(nèi)存的首地址
    返回值:成功 0, 失敗 -1

    ④shmctl()函數(shù)

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    功能:對共享內(nèi)存進(jìn)行操作刪除共享內(nèi)存,共享內(nèi)存要?jiǎng)h除才會消失,創(chuàng)建共享內(nèi)存的進(jìn)行被銷毀了對共享內(nèi)存是沒有任何影響。
    參數(shù):

    • shmid: 共享內(nèi)存的ID
    • cmd : 要做的操作
      - IPC_STAT : 獲取共享內(nèi)存的當(dāng)前的狀態(tài)
      - IPC_SET : 設(shè)置共享內(nèi)存的狀態(tài)
      - IPC_RMID: 標(biāo)記共享內(nèi)存被銷毀
    • buf:需要設(shè)置或者獲取的共享內(nèi)存的屬性信息
      - IPC_STAT : buf存儲數(shù)據(jù)
      - IPC_SET : buf中需要初始化數(shù)據(jù),設(shè)置到內(nèi)核中
      - IPC_RMID : 沒有用,NULL

    ⑤ftok()函數(shù)

    key_t ftok(const char *pathname, int proj_id);

    功能:根據(jù)指定的路徑名,和int值,生成一個(gè)共享內(nèi)存的key
    參數(shù):

    • pathname:指定一個(gè)存在的路徑
      /home/nowcoder/Linux/a.txt
      /
    • proj_id: int類型的值,但是這系統(tǒng)調(diào)用只會使用其中的1個(gè)字節(jié)
      范圍 : 0-255 一般指定一個(gè)字符 ‘a(chǎn)’

    問題1:操作系統(tǒng)如何知道一塊共享內(nèi)存被多少個(gè)進(jìn)程關(guān)聯(lián)?

    共享內(nèi)存維護(hù)了一個(gè)結(jié)構(gòu)體struct shmid_ds 這個(gè)結(jié)構(gòu)體中有一個(gè)成員 shm_nattch;
    shm_nattach 記錄了關(guān)聯(lián)的進(jìn)程個(gè)數(shù)。

    問題2:可不可以對共享內(nèi)存進(jìn)行多次刪除 shmctl

    (這塊可以看視頻課中的02:20:45

    可以的,因?yàn)閟hmctl 標(biāo)記刪除共享內(nèi)存,不是直接刪除;

    那么什么時(shí)候真正刪除呢?
    當(dāng)和共享內(nèi)存關(guān)聯(lián)的進(jìn)程數(shù)為0的時(shí)候,就真正被刪除;
    當(dāng)共享內(nèi)存的key為0的時(shí)候,表示共享內(nèi)存被標(biāo)記刪除了;
    如果一個(gè)進(jìn)程和共享內(nèi)存取消關(guān)聯(lián),那么這個(gè)進(jìn)程就不能繼續(xù)操作這個(gè)共享內(nèi)存,也不能進(jìn)行關(guān)聯(lián)。

    ☆☆☆共享內(nèi)存和內(nèi)存映射的區(qū)別

    (參考mmap映射區(qū)和shm共享內(nèi)存的區(qū)別總結(jié))

  • 共享內(nèi)存可以直接創(chuàng)建,內(nèi)存映射需要磁盤文件(匿名映射除外)
  • 共享內(nèi)存效果更好
  • 內(nèi)存
    所有的進(jìn)程操作的是同一塊共享內(nèi)存
    內(nèi)存映射:每個(gè)進(jìn)程在自己的虛擬地址空間中有一個(gè)獨(dú)立的內(nèi)存
  • 數(shù)據(jù)安全
    進(jìn)程突然退出:共享內(nèi)存還存在;內(nèi)存映射區(qū)消失;
    運(yùn)行進(jìn)程的電腦死機(jī),宕機(jī)了:數(shù)據(jù)存在在共享內(nèi)存中,沒有了;內(nèi)存映射區(qū)的數(shù)據(jù) ,由于磁盤文件中的數(shù)據(jù)還在,所以內(nèi)存映射區(qū)的數(shù)據(jù)還存在
  • 生命周期
    內(nèi)存映射區(qū):進(jìn)程退出,內(nèi)存映射區(qū)銷毀;
    共享內(nèi)存:進(jìn)程退出,共享內(nèi)存還在,標(biāo)記刪除(所有的關(guān)聯(lián)的進(jìn)程數(shù)為0),或者關(guān)機(jī);
    如果一個(gè)進(jìn)程退出,會自動(dòng)和共享內(nèi)存進(jìn)行取消關(guān)聯(lián)。
  • linux中的兩種共享內(nèi)存:一種是我們的IPC通信System V版本的共享內(nèi)存shm,另外的一種就是我們今天提到的存儲映射I/Ommap函數(shù));
    (一共有三種方式創(chuàng)建共享內(nèi)存:POSIX共享內(nèi)存對象、System V共享內(nèi)存段、使用mmap()函數(shù)創(chuàng)建的共享映射區(qū))

    總結(jié)mmap和shm:
    1、mmap是在磁盤上建立一個(gè)文件,每個(gè)進(jìn)程的地址空間中開辟出一塊空間進(jìn)行映射
    而對于shm而言,shm每個(gè)進(jìn)程最終會映射到同一塊物理內(nèi)存。shm保存在物理內(nèi)存,這樣讀寫的速度要比磁盤要快,但是存儲量不是特別大。
    2、相對于shm來說,mmap更加簡單,調(diào)用更加方便,所以這也是大家都喜歡用的原因。
    3、另外mmap有一個(gè)好處是當(dāng)機(jī)器重啟,因?yàn)閙map把文件保存在磁盤上,這個(gè)文件還保存了操作系統(tǒng)同步的映像,所以mmap不會丟失,但是shmget就會丟失。

    mmp的中介是磁盤上的一個(gè)文件,每個(gè)進(jìn)程在自己的虛擬地址空間中有一個(gè)獨(dú)立的內(nèi)存,所以涉及到i/O操作,因此讀寫速度慢,但電腦宕機(jī)后文件依然存在;
    shm的中介是同一塊物理內(nèi)存,所以讀寫速度快,但如果電腦重啟,創(chuàng)建的共享內(nèi)存就沒了。

    (參考鏈接:點(diǎn)這里)
    可以看到內(nèi)存映射中需要的一個(gè)參數(shù)是int fd(文件的標(biāo)識符),可見函數(shù)是通過fd將文件內(nèi)容映射到一個(gè)內(nèi)存空間;
    訪問共享內(nèi)存的執(zhí)行速度比直接訪問文件的快N倍(N>>10),這對于要求快速輸入輸出的場合非常有效;
    共享內(nèi)存主要是為了提高程序的執(zhí)行速度,方便多個(gè)進(jìn)程進(jìn)行快速的大數(shù)據(jù)量的交換;
    內(nèi)存映射是用來加快對文件/設(shè)備的訪問(如果是大文件,而且還想提高讀寫速度的話,建議使用內(nèi)存映射);
    共享內(nèi)存是用來在多個(gè)進(jìn)程間進(jìn)行快速的大數(shù)據(jù)量的交換;
    可以在程序中指定要將文件內(nèi)容映射到哪塊內(nèi)存。對于多個(gè)進(jìn)程打開同一個(gè)文件,不同的內(nèi)存映射可以開辟多塊內(nèi)存區(qū)域。
    內(nèi)存映射是為了加快對文件/設(shè)備的訪問速度,不是用來進(jìn)行數(shù)據(jù)通信的;

    我對內(nèi)存映射的理解就是通過操作內(nèi)存來實(shí)現(xiàn)對文件的操作,這樣可以加快執(zhí)行速度,因?yàn)椴僮鲀?nèi)存比操作文件的速度快多了!
    共享內(nèi)存,顧名思義,就是預(yù)留出的內(nèi)存區(qū)域,它允許一組進(jìn)程對其訪問。
    共享內(nèi)存是system vIPC中三種通信機(jī)制最快的一種,也是最簡單的一種。對于進(jìn)程來說,
    獲得共享內(nèi)存后,他對內(nèi)存的使用和其他的內(nèi)存是一樣的。由一個(gè)進(jìn)程對共享內(nèi)存所進(jìn)行的
    操作對其他進(jìn)程來說都是立即可見的,因?yàn)槊總€(gè)進(jìn)程只需要通過一個(gè)指向共享內(nèi)存空間的指針就可以來讀取
    共享內(nèi)存中的內(nèi)容(說白了就好比申請了一塊內(nèi)存,每個(gè)需要的進(jìn)程都有一個(gè)指針指向這個(gè)內(nèi)存)
    就可以輕松獲得結(jié)果。使用共享內(nèi)存要注意的問題:共享內(nèi)存不能確保對內(nèi)存操作的互斥性。
    一個(gè)進(jìn)程可以向共享內(nèi)存中的給定地址寫入,而同時(shí)另一個(gè)進(jìn)程從相同的地址讀出,這將會導(dǎo)致不一致的數(shù)據(jù)。
    因此使用共享內(nèi)存的進(jìn)程必須自己保證讀操作和寫操作的的嚴(yán)格互斥。
    可使用鎖和原子操作解決這一問題。也可使用信號量保證互斥訪問共享內(nèi)存區(qū)域。
    共享內(nèi)存在一些情況下可以代替消息隊(duì)列,而且共享內(nèi)存的讀/寫比使用消息隊(duì)列要快!

    共享內(nèi)存操作命令(打印當(dāng)前系統(tǒng)中所有的 進(jìn)程間通信方式 的信息)

    ipcs 用法:

    ipcs -a // 打印當(dāng)前系統(tǒng)中所有的進(jìn)程間通信方式的信息 ipcs -m // 打印出使用共享內(nèi)存進(jìn)行進(jìn)程間通信的信息 ipcs -q // 打印出使用消息隊(duì)列進(jìn)行進(jìn)程間通信的信息 ipcs -s // 打印出使用信號進(jìn)行進(jìn)程間通信的信息

    ipcrm 用法:

    ipcrm -M shmkey // 移除用shmkey創(chuàng)建的共享內(nèi)存段 ipcrm -m shmid // 移除用shmid標(biāo)識的共享內(nèi)存段 ipcrm -Q msgkey // 移除用msqkey創(chuàng)建的消息隊(duì)列 ipcrm -q msqid // 移除用msqid標(biāo)識的消息隊(duì)列 ipcrm -S semkey // 移除用semkey創(chuàng)建的信號 ipcrm -s semid // 移除用semid標(biāo)識的信號

    示例

    這塊的示例不好,可以看①Linux簡明系統(tǒng)編程(嵌入式公眾號的課)—總課時(shí)12h中的 ☆☆☆②任務(wù)間的通信 之 共享內(nèi)存 shared memory

    read_shm.c

    #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h>int main() { // 1.獲取一個(gè)共享內(nèi)存int shmid = shmget(100, 0, IPC_CREAT);printf("shmid : %d\n", shmid);// 2.和當(dāng)前進(jìn)程進(jìn)行關(guān)聯(lián)void * ptr = shmat(shmid, NULL, 0);// 3.讀數(shù)據(jù)printf("%s\n", (char *)ptr);printf("按任意鍵繼續(xù)\n");getchar();// 4.解除關(guān)聯(lián)shmdt(ptr);// 5.刪除共享內(nèi)存shmctl(shmid, IPC_RMID, NULL);return 0; }

    write_shm.c

    #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h>int main() { // 1.創(chuàng)建一個(gè)共享內(nèi)存int shmid = shmget(100, 4096, IPC_CREAT|0664);printf("shmid : %d\n", shmid);// 2.和當(dāng)前進(jìn)程進(jìn)行關(guān)聯(lián)void * ptr = shmat(shmid, NULL, 0);char * str = "helloworld";// 3.寫數(shù)據(jù)memcpy(ptr, str, strlen(str) + 1);printf("按任意鍵繼續(xù)\n");getchar();//按任意鍵// 4.解除關(guān)聯(lián)shmdt(ptr);// 5.刪除共享內(nèi)存shmctl(shmid, IPC_RMID, NULL);return 0; }

    分別生成對應(yīng)的可執(zhí)行文件,用兩個(gè)終端分別打開。

    ④信號signal

    (視頻課從12:34開始)
    (可以參考①Linux簡明系統(tǒng)編程(嵌入式公眾號的課)—總課時(shí)12h中的 第20節(jié)課:信號signal

    信號的基本概念

    信號是 Linux 進(jìn)程間通信的最古老的方式之一,是事件發(fā)生時(shí)對進(jìn)程的通知機(jī)制,有時(shí)也稱之為軟件中斷,它是在軟件層次上對中斷機(jī)制的一種模擬,是一種異步通信的方式。信號可以導(dǎo)致一個(gè)正在運(yùn)行的進(jìn)程被另一個(gè)正在運(yùn)行的異步進(jìn)程中斷,轉(zhuǎn)而處理某一個(gè)突發(fā)事件。

    發(fā)往進(jìn)程的諸多信號,通常都是源于內(nèi)核。引發(fā)內(nèi)核為進(jìn)程產(chǎn)生信號的各類事件如下

    • 對于前臺進(jìn)程,用戶可以通過輸入特殊的終端字符來給它發(fā)送信號。比如輸入Ctrl+C 通常會給進(jìn)程發(fā)送一個(gè)中斷信號
    • 硬件發(fā)生異常,即硬件檢測到一個(gè)錯(cuò)誤條件并通知內(nèi)核,隨即再由內(nèi)核發(fā)送相應(yīng)信號給相關(guān)進(jìn)程;比如執(zhí)行一條異常的機(jī)器語言指令,諸如被 0 除,或者引用了無法訪問的內(nèi)存區(qū)域。
    • 系統(tǒng)狀態(tài)變化,比如 alarm 定時(shí)器到期將引起 SIGALRM 信號,進(jìn)程執(zhí)行的 CPU 時(shí)間超限,或者該進(jìn)程的某個(gè)子進(jìn)程退出。
    • 運(yùn)行 kill 命令或調(diào)用 kill 函數(shù)

    (前臺進(jìn)程./a.out; 后臺進(jìn)程 ./a.out &)

    使用信號的兩個(gè)主要目的是:

    • 讓進(jìn)程知道已經(jīng)發(fā)生了一個(gè)特定的事情;
    • 強(qiáng)迫進(jìn)程執(zhí)行它自己代碼中的信號處理程序。

    信號的特點(diǎn):

    • 簡單;
    • 不能攜帶大量信息;
    • 滿足某個(gè)特定條件才發(fā)送;
    • 優(yōu)先級比較高

    查看系統(tǒng)定義的信號列表:

    kill –l

    前 31 個(gè)信號為常規(guī)信號,其余為實(shí)時(shí)信號

    $ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX

    常用的信號:

    編號信號名稱對應(yīng)事件默認(rèn)動(dòng)作
    2SIGINT
    interrupt
    當(dāng)用戶按下了<Ctrl+C>組合鍵時(shí),用戶終端向正在運(yùn)行中的由該終端啟動(dòng)的程序發(fā)出此信號終止進(jìn)程
    3SIGQUIT用戶按下<Ctrl+\>組合鍵時(shí)產(chǎn)生該信號,用戶終端向正在運(yùn)行中的由該終端啟動(dòng)的程序發(fā)出些信號終止進(jìn)程
    9SIGKILL無條件終止進(jìn)程。該信號不能被忽略,處理和阻塞終止進(jìn)程,可以殺死任何進(jìn)程(除了僵尸進(jìn)程)
    11SIGSEGV指示進(jìn)程進(jìn)行了無效內(nèi)存訪問(段錯(cuò)誤segment fault)終止進(jìn)程,并產(chǎn)生core文件
    (視頻課中26:4531:13
    13SIGPIPEBroken pipe向一個(gè)沒有讀端的管道寫數(shù)據(jù)終止進(jìn)程
    14SIGALARM定時(shí)器超時(shí),超時(shí)的時(shí)間 由系統(tǒng)調(diào)用alarm設(shè)置終止進(jìn)程
    17SIGCHLD
    child
    子進(jìn)程結(jié)束時(shí),父進(jìn)程會收到這個(gè)信號忽略這個(gè)信號
    18SIGCONT
    continue
    如果進(jìn)程已停止,則使其繼續(xù)運(yùn)行繼續(xù)/忽略
    19SIGSTOP停止進(jìn)程的執(zhí)行。該信號不能被忽略,處理和阻塞終止進(jìn)程

    如果程序產(chǎn)生了段錯(cuò)誤(Segment Fault),一般是因?yàn)樵L問了非法內(nèi)存,那么就可以通過生成core文件的方式來找到程序具體哪里出了問題;
    修改core文件大小
    編譯生成可執(zhí)行程序時(shí)加上GDB選項(xiàng);
    然后運(yùn)行可執(zhí)行程序時(shí)就會生成一個(gè)core文件;
    通過GDB調(diào)試,輸入core-file core,就可以看出來是哪里的代碼導(dǎo)致了段錯(cuò)誤。

    core.c

    #include <stdio.h> #include <string.h>int main() {char * buf;strcpy(buf, "hello");return 0; }



    查看信號的詳細(xì)信息:

    man 7 signal

    信號的 5 種默認(rèn)處理動(dòng)作

    • Term 終止進(jìn)程
    • Ign 當(dāng)前進(jìn)程忽略掉這個(gè)信號
    • Core 終止進(jìn)程,并生成一個(gè)Core文件
    • Stop 暫停當(dāng)前進(jìn)程
    • Cont 繼續(xù)執(zhí)行當(dāng)前被暫停的進(jìn)程

    信號的幾種狀態(tài):產(chǎn)生、未決、遞達(dá)

    SIGKILLkill -9) 和 SIGSTOPkill -19) 信號不能被捕捉、阻塞或者忽略,只能執(zhí)行默認(rèn)動(dòng)作。

    信號相關(guān)的函數(shù)1:kill()、raise()、abort()、alarm()、setitimer()

    int kill(pid_t pid, int sig); int raise(int sig); void abort(void); unsigned int alarm(unsigned int seconds); int setitimer(int which, const struct itimerval *new_val, struct itimerval *old_value);
    ①kill()函數(shù):給任何的進(jìn)程發(fā)送任何的信號 sig
    #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);

    功能:給任何的進(jìn)程或者進(jìn)程組pid, 發(fā)送任何的信號 sig
    參數(shù):

    • pid :
      >0:將信號發(fā)送給指定的進(jìn)程
      = 0 : 將信號發(fā)送給當(dāng)前的進(jìn)程組
      = -1 : 將信號發(fā)送給每一個(gè)有權(quán)限接收這個(gè)信號的進(jìn)程
      < -1 : 這個(gè)pid=某個(gè)進(jìn)程組的ID取反 (-12345)
    • sig : 需要發(fā)送的信號的編號或者是宏值,0表示不發(fā)送任何信號
    • 例如:kill(getppid(), 9); 和 kill(getpid(), 9);

    示例:

    #include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h>int main() {pid_t pid = fork();if(pid == 0) {// 子進(jìn)程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父進(jìn)程printf("parent process\n");sleep(6);printf("kill child process now\n");kill(pid, SIGINT);}return 0; }

    結(jié)果:

    child process parent process child process child process child process child process kill child process now
    ②raise()函數(shù):給當(dāng)前進(jìn)程發(fā)送信號
    int raise(int sig);

    功能:給當(dāng)前進(jìn)程發(fā)送信號
    參數(shù): sig : 要發(fā)送的信號
    返回值: 成功 0;失敗 非0
    相當(dāng)于是kill(getpid(), sig);

    ③abort()函數(shù):殺死當(dāng)前進(jìn)程
    void abort(void);

    功能: 發(fā)送SIGABRT信號給當(dāng)前的進(jìn)程,殺死當(dāng)前進(jìn)程
    相當(dāng)于是kill(getpid(), SIGABRT);

    ☆☆☆④alarm()函數(shù):設(shè)置定時(shí)器(鬧鐘),定時(shí)時(shí)間到了就終止當(dāng)前的進(jìn)程
    #include <unistd.h> unsigned int alarm(unsigned int seconds);

    功能:設(shè)置定時(shí)器(鬧鐘)。函數(shù)調(diào)用,開始倒計(jì)時(shí),當(dāng)?shù)褂?jì)時(shí)為0的時(shí)候,函數(shù)會給當(dāng)前的進(jìn)程發(fā)送一個(gè)信號:SIGALARM,
    參數(shù):seconds 倒計(jì)時(shí)的時(shí)長,單位:秒。如果參數(shù)為0,定時(shí)器無效(不進(jìn)行倒計(jì)時(shí),不發(fā)信號),就相當(dāng)于是取消一個(gè)定時(shí)器,通過alarm(0)。

    返回值:

    • 之前沒有定時(shí)器,返回0;
    • 之前有定時(shí)器,返回之前的定時(shí)器剩余的時(shí)間

    信號SIGALARM :默認(rèn)終止當(dāng)前的進(jìn)程,每一個(gè)進(jìn)程都有且只有唯一的一個(gè)定時(shí)器。

    例如:
    alarm(10); -> 返回0
    過了1秒
    alarm(5); -> 返回9

    alarm(100) -> 該函數(shù)是不阻塞的

    示例1:設(shè)置一個(gè)定時(shí)器
    #include <stdio.h> #include <unistd.h>int main() { 設(shè)置一個(gè)dingint seconds = alarm(5);printf("seconds = %d\n", seconds); // 0 之前沒有定時(shí)器,所以返回0sleep(2);seconds = alarm(2); // 不阻塞printf("seconds = %d\n", seconds); // 3 之前有定時(shí)器,所以返回之前的定時(shí)器剩余的時(shí)間while(1) {}//這里雖然是死循環(huán),但是當(dāng)定時(shí)時(shí)間到了的時(shí)候就進(jìn)程就會結(jié)束return 0; }
    示例2:電腦1秒鐘能數(shù)多少個(gè)數(shù)?
    #include <stdio.h> #include <unistd.h>/*實(shí)際的時(shí)間 = 內(nèi)核時(shí)間 + 用戶時(shí)間 + 消耗的時(shí)間進(jìn)行文件IO操作的時(shí)候比較浪費(fèi)時(shí)間定時(shí)器,與進(jìn)程的狀態(tài)無關(guān)(自然定時(shí)法)。無論進(jìn)程處于什么狀態(tài),alarm都會計(jì)時(shí)。 */int main() { alarm(1);//定時(shí)1秒int i = 0;while(1) {printf("%i\n", i++);}return 0; }

    由于打印操作很耗費(fèi)時(shí)間,所以結(jié)果不準(zhǔn)確(才93361),可以直接將數(shù)字存到一個(gè)文件中./a.out >> a.txt(>>重定向操作),試了一下文件中有500多萬行數(shù)據(jù),文件大概70多M。

    ⑤setitimer()函數(shù):(既可以用來延時(shí)執(zhí)行,也可定時(shí)執(zhí)行;可以實(shí)現(xiàn)周期性定時(shí))

    (見linux c setitimer用法說明)

    #include <sys/time.h>int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

    功能:設(shè)置定時(shí)器(鬧鐘)。可以替代alarm函數(shù)。精度微妙us,可以實(shí)現(xiàn)周期性定時(shí)
    參數(shù):

    • which : 定時(shí)器以什么時(shí)間計(jì)時(shí)
      ITIMER_REAL: 真實(shí)時(shí)間,時(shí)間到達(dá),發(fā)送 SIGALRM;(常用
      ITIMER_VIRTUAL: 用戶時(shí)間,時(shí)間到達(dá),發(fā)送 SIGVTALRM;
      ITIMER_PROF: 以該進(jìn)程在用戶態(tài)和內(nèi)核態(tài)下所消耗的時(shí)間來計(jì)算,時(shí)間到達(dá),發(fā)送 SIGPROF
    • new_value: 設(shè)置定時(shí)器的屬性
    • old_value :記錄上一次的定時(shí)的時(shí)間參數(shù),一般不使用,直接寫NULL

    返回值:成功 0;失敗 -1,并設(shè)置錯(cuò)誤號。

    結(jié)構(gòu)體struct itimerval:

    // 定時(shí)器的結(jié)構(gòu)體 struct itimerval { struct timeval it_interval; // 每個(gè)階段的時(shí)間,間隔時(shí)間struct timeval it_value; // 延遲多長時(shí)間執(zhí)行定時(shí)器 };struct timeval { // 時(shí)間的結(jié)構(gòu)體time_t tv_sec; // 秒數(shù) suseconds_t tv_usec; // 微秒 };

    例如:it_value設(shè)定為10s,it_interval設(shè)定為2s,表示先倒計(jì)時(shí)10秒,然后循環(huán)倒計(jì)時(shí)2秒

    示例:

    #include <stdio.h> #include <signal.h> #include <sys/time.h> #include<unistd.h>void signalHandler(int signo) {switch (signo){case SIGALRM:printf("Caught the SIGALRM signal!\n");break;} } //先倒計(jì)時(shí)5s,然后循環(huán)倒計(jì)時(shí)10s int main(int argc, char *argv[]) {//捕捉SIGALRM信號:signal(SIGALRM, signalHandler);struct itimerval new_value, old_value;//延遲的時(shí)間,5s之后開始第一次定時(shí)new_value.it_value.tv_sec = 5;new_value.it_value.tv_usec = 0;//間隔的時(shí)間:每次定時(shí)10snew_value.it_interval.tv_sec = 10;new_value.it_interval.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, &old_value);//printf("定時(shí)器開始了...\n");if(ret == -1) {perror("setitimer");exit(0);}for(int i = 0; i < 30; +i) {printf("%d\n", ++i);sleep(1);}return 0; }

    結(jié)果:先倒計(jì)時(shí)5s,然后循環(huán)倒計(jì)時(shí)10s

    1 2 3 4 5 Caught the SIGALRM signal! 6 7 8 9 10 11 12 13 14 15 Caught the SIGALRM signal! 16 17 18 19 20 21 22 23 24 25 Caught the SIGALRM signal! 26 27 28 29 30

    信號相關(guān)的函數(shù)2:(信號捕捉函數(shù))signal()函數(shù)、sigaction()函數(shù)

    信號捕捉函數(shù) — signal()函數(shù):
    (見①Linux簡明系統(tǒng)編程(嵌入式公眾號的課)—總課時(shí)12h中的博客1

    #include <signal.h> typedef void (*sighandler_t)(int);//函數(shù)指針 sighandler_t signal(int signum, sighandler_t handler);

    功能:設(shè)置某個(gè)信號的捕捉行為
    參數(shù):

    • signum: 要捕捉的信號
    • handler: 捕捉到信號要如何處理
      ①SIG_IGN : 忽略信號
      ②SIG_DFL : 使用信號默認(rèn)的行為
      ③回調(diào)函數(shù)handler : 這個(gè)函數(shù)是內(nèi)核調(diào)用,程序員只負(fù)責(zé)寫,捕捉到信號后如何去處理信號。
      回調(diào)函數(shù):
      - 需要程序員實(shí)現(xiàn),提前準(zhǔn)備好的,函數(shù)的類型根據(jù)實(shí)際需求,看函數(shù)指針的定義
      - 不是程序員調(diào)用,而是當(dāng)信號產(chǎn)生,由內(nèi)核調(diào)用
      - 函數(shù)指針是實(shí)現(xiàn)回調(diào)的手段,函數(shù)實(shí)現(xiàn)之后,將函數(shù)名放到函數(shù)指針的位置就可以了

    返回值:

    • 成功,返回上一次注冊的信號處理函數(shù)的地址。第一次調(diào)用返回NULL;
    • 失敗,返回SIG_ERR,設(shè)置錯(cuò)誤號。

    注意:SIGKILL 和 SIGSTOP不能被捕捉,不能被忽略。

    示例:
    (見上面setitimer()函數(shù)中的示例)

    ②sigaction()函數(shù):

    #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    功能:檢查或者改變信號的處理;信號捕捉
    參數(shù):

    • signum : 需要捕捉的信號的編號或者宏值(信號的名稱)
    • act :捕捉到信號之后的處理動(dòng)作
    • oldact : 上一次對信號捕捉相關(guān)的設(shè)置,一般不使用,直接寫NULL

    返回值: 成功 0;失敗 -1

    結(jié)構(gòu)體struct sigaction:

    struct sigaction {// 函數(shù)指針,指向的函數(shù)就是信號捕捉到之后的處理函數(shù)void (*sa_handler)(int);// 不常用void (*sa_sigaction)(int, siginfo_t *, void *);// 臨時(shí)阻塞信號集,在信號捕捉函數(shù)執(zhí)行過程中,臨時(shí)阻塞某些信號。sigset_t sa_mask;// 使用哪一個(gè)信號處理對捕捉到的信號進(jìn)行處理// 這個(gè)值可以是0,表示使用sa_handler;也可以是SA_SIGINFO,表示使用sa_sigactionint sa_flags;// 被廢棄掉了void (*sa_restorer)(void); };

    示例:

    #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include<unistd.h>void myalarm(int num) {printf("捕捉到了信號的編號是:%d\n", num);printf("xxxxxxx\n"); } void signalHandler(int signo) {switch (signo){case SIGALRM:printf("Caught the SIGALRM signal!\n");break;} }// 過3秒以后,每隔2秒鐘定時(shí)一次 int main() {struct sigaction act;act.sa_flags = 0;act.sa_handler = myalarm;//signalHandler;//sigemptyset(&act.sa_mask); // 清空臨時(shí)阻塞信號集// 注冊信號捕捉sigaction(SIGALRM, &act, NULL);struct itimerval new_value;// 設(shè)置間隔的時(shí)間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設(shè)置延遲的時(shí)間,3秒之后開始第一次定時(shí)new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的//printf("定時(shí)器開始了...\n");if(ret == -1) {perror("setitimer");exit(0);}//while(1);for(int i = 0; i < 30; +i) {printf("%d\n", ++i);sleep(1);}return 0; }

    結(jié)果:

    1 2 3 捕捉到了信號的編號是:14 xxxxxxx 4 5 捕捉到了信號的編號是:14 xxxxxxx 6 7 捕捉到了信號的編號是:14 xxxxxxx 8 9 捕捉到了信號的編號是:14 xxxxxxx 10 11 捕捉到了信號的編號是:14 xxxxxxx

    信號集(信號的阻塞 — 防止信號打斷敏感的操作)

    (視頻課從56:50開始到01:02:10

    許多信號相關(guān)的系統(tǒng)調(diào)用都需要能表示一組不同的信號,多個(gè)信號可使用一個(gè)稱之為信號集的數(shù)據(jù)結(jié)構(gòu)來表示,其系統(tǒng)數(shù)據(jù)類型為 sigset_t。
    在 PCB 中有兩個(gè)非常重要的信號集。一個(gè)稱之為 “阻塞信號集” ,另一個(gè)稱之為 “未決信號集” 。這兩個(gè)信號集都是內(nèi)核使用位圖機(jī)制來實(shí)現(xiàn)的,但操作系統(tǒng)不允許我們直接對這兩個(gè)信號集進(jìn)行位操作,而需要自定義另外一個(gè)集合,借助信號集操作函數(shù)來對 PCB 中的這兩個(gè)信號集進(jìn)行修改

    • 信號的 “未決” 是一種狀態(tài),指的是從信號的產(chǎn)生信號被處理前的這一段時(shí)間;
    • 信號的 “阻塞” 是一個(gè)開關(guān)動(dòng)作,指的是阻止信號被處理,但不是阻止信號產(chǎn)生。

    信號的阻塞就是讓系統(tǒng)暫時(shí)保留信號留待以后發(fā)送。由于另外有辦法讓系統(tǒng)忽略信號,所以一般情況下信號的阻塞只是暫時(shí)的,只是為了防止信號打斷敏感的操作

    阻塞信號機(jī) & 未決信號機(jī):

  • 用戶通過鍵盤 Ctrl + C, 產(chǎn)生2號信號SIGINT (信號被創(chuàng)建)
  • 信號產(chǎn)生但是沒有被處理 (未決)
    • 在內(nèi)核中將所有沒被處理的信號存儲在一個(gè)集合中; (未決信號集
    • SIGINT信號狀態(tài)被存儲在第二個(gè)標(biāo)志位上;
      — 這個(gè)標(biāo)志位的值為0, 說明信號不是未決狀態(tài)
      — 這個(gè)標(biāo)志位的值為1, 說明信號處于未決狀態(tài)
  • 當(dāng)某個(gè)未決狀態(tài)的信號需要被處理時(shí),處理之前需要和另一個(gè)信號集(阻塞信號集)進(jìn)行比較:
    • 阻塞信號集默認(rèn)不阻塞任何的信號;
    • 如果想要阻塞某些信號需要用戶調(diào)用系統(tǒng)的API;(見下面的信號集相關(guān)操作函數(shù)
  • 在處理的時(shí)候和阻塞信號集中的標(biāo)志位進(jìn)行查詢,看是不是對該信號設(shè)置了阻塞
    • 如果沒有阻塞,這個(gè)信號就被處理;
    • 如果阻塞了,這個(gè)信號就繼續(xù)處于未決狀態(tài),直到阻塞解除,這個(gè)信號才會被處理。
  • 信號集相關(guān)操作函數(shù)
    一、對自定義的信號集進(jìn)行修改: int sigemptyset(sigset_t *set);//清空臨時(shí)阻塞信號集 int sigfillset(sigset_t *set);//全部置一 int sigaddset(sigset_t *set, int signum);//將某個(gè)信號置一 int sigdelset(sigset_t *set, int signum);//將某個(gè)信號清零 int sigismember(const sigset_t *set, int signum);//判斷某個(gè)信號的狀態(tài)是否為1二、對內(nèi)核中的兩個(gè)信號集進(jìn)行修改: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigpending(sigset_t *set);

    下面分別介紹一和二:

    一、對自定義的信號集進(jìn)行修改

    以下信號集相關(guān)的函數(shù)都是對自定義的信號集進(jìn)行操作
    ①sigemptyset()函數(shù)

    int sigemptyset(sigset_t *set);

    功能:清空信號集中的數(shù)據(jù),將信號集中的所有的標(biāo)志位 置為0
    參數(shù):set,傳出參數(shù),需要操作的信號集
    返回值:成功返回0, 失敗返回-1

    ②sigfillset()函數(shù)

    int sigfillset(sigset_t *set);

    功能:將信號集中的所有的標(biāo)志位置為1
    參數(shù):set,傳出參數(shù),需要操作的信號集
    返回值:成功返回0, 失敗返回-1

    ③sigaddset()函數(shù)

    int sigaddset(sigset_t *set, int signum);

    功能:設(shè)置信號集中的某一個(gè)信號對應(yīng)的標(biāo)志位為1,表示阻塞這個(gè)信號
    參數(shù):

    • set:傳出參數(shù),需要操作的信號集
    • signum:需要設(shè)置阻塞的那個(gè)信號

    返回值:成功返回0, 失敗返回-1

    ④sigdelset()函數(shù)

    int sigdelset(sigset_t *set, int signum);

    功能:設(shè)置信號集中的某一個(gè)信號對應(yīng)的標(biāo)志位為0,表示不阻塞這個(gè)信號
    參數(shù):

    • set:傳出參數(shù),需要操作的信號集
    • signum:需要設(shè)置不阻塞的那個(gè)信號

    返回值:成功返回0, 失敗返回-1

    ⑤sigismember()函數(shù)

    int sigismember(const sigset_t *set, int signum);

    功能:判斷某個(gè)信號是否阻塞,即它對應(yīng)的標(biāo)志位是否為1
    參數(shù):

    • set:需要操作的信號集
    • signum:需要判斷的那個(gè)信號

    返回值:

    • 1 : signum被阻塞
    • 0 : signum不阻塞
    • -1 : 失敗

    ⑥示例:對自定義的信號集進(jìn)行修改

    #include <signal.h> #include <stdio.h>int main() {// 創(chuàng)建一個(gè)信號集sigset_t set;// 清空信號集的內(nèi)容sigemptyset(&set);// 判斷 SIGINT 是否在信號集 set 里int ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 添加幾個(gè)信號到信號集中sigaddset(&set, SIGINT);//Ctrl+csigaddset(&set, SIGQUIT);//Ctrl+\ // 判斷SIGINT是否在信號集中ret = sigismember(&set, SIGINT);if(ret == 0) {printf("SIGINT 不阻塞\n");} else if(ret == 1) {printf("SIGINT 阻塞\n");}// 判斷SIGQUIT是否在信號集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}// 從信號集中刪除一個(gè)信號sigdelset(&set, SIGQUIT);// 判斷SIGQUIT是否在信號集中ret = sigismember(&set, SIGQUIT);if(ret == 0) {printf("SIGQUIT 不阻塞\n");} else if(ret == 1) {printf("SIGQUIT 阻塞\n");}return 0; }

    編譯運(yùn)行:

    SIGINT 不阻塞 SIGINT 阻塞 SIGQUIT 阻塞 SIGQUIT 不阻塞

    二、對內(nèi)核中的兩個(gè)信號集進(jìn)行修改
    sigprocmask()函數(shù):

    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

    功能:將自定義信號集中的數(shù)據(jù)設(shè)置到內(nèi)核中(設(shè)置阻塞,解除阻塞,替換)
    參數(shù):

    • how : 如何對內(nèi)核阻塞信號集進(jìn)行處理
      <1>SIG_BLOCK: 將用戶設(shè)置的阻塞信號集添加到內(nèi)核中,內(nèi)核中原來的數(shù)據(jù)不變
      假設(shè)內(nèi)核中默認(rèn)的阻塞信號集是mask, mask | set
      <2>SIG_UNBLOCK: 根據(jù)用戶設(shè)置的數(shù)據(jù),對內(nèi)核中的數(shù)據(jù)進(jìn)行解除阻塞
      mask &= ~set
      <3>SIG_SETMASK:覆蓋內(nèi)核中原來的值
    • set :已經(jīng)初始化好的用戶自定義的信號集
    • oldset : 保存設(shè)置之前的內(nèi)核中的阻塞信號集的狀態(tài),可以是 NULL

    返回值:成功:0;失敗:-1,設(shè)置錯(cuò)誤號:EFAULT、EINVAL

    sigpending()函數(shù):

    int sigpending(sigset_t *set);

    功能:獲取內(nèi)核中的未決信號集
    參數(shù):set,傳出參數(shù),保存的是內(nèi)核中的未決信號集中的信息

    ③示例:對內(nèi)核中的兩個(gè)信號集進(jìn)行修改

    // 編寫一個(gè)程序,把所有的常規(guī)信號(1-31)的未決狀態(tài)打印到屏幕 // 設(shè)置某些信號是阻塞的,通過鍵盤產(chǎn)生這些信號 #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h>int main() {// 設(shè)置2、3號信號阻塞sigset_t set;//自定義信號集sigemptyset(&set);//全部清零// 將2號和3號信號添加到信號集中sigaddset(&set, SIGINT);//Ctrl+csigaddset(&set, SIGQUIT);//Ctrl+\ // 修改內(nèi)核中的阻塞信號集sigprocmask(SIG_BLOCK, &set, NULL);int num = 0;while(1) {num++;// 獲取當(dāng)前的未決信號集的數(shù)據(jù)sigset_t pendingset;sigemptyset(&pendingset);//全部清零sigpending(&pendingset);//獲取內(nèi)核中的未決信號集中的信息// 遍歷前32位for(int i = 1; i <= 31; i++) {if(sigismember(&pendingset, i) == 1) {printf("1");}else if(sigismember(&pendingset, i) == 0) {printf("0");}else {perror("sigismember");exit(0);}}printf("\n");sleep(1);if(num == 10) {//循環(huán)10次// 解除阻塞,就只執(zhí)行信號,而執(zhí)行信號的結(jié)果就是結(jié)束進(jìn)程sigprocmask(SIG_UNBLOCK, &set, NULL);}}return 0; }

    編譯運(yùn)行:

    內(nèi)核實(shí)現(xiàn)信號捕捉的過程

    補(bǔ)充:SIGCHLD信號(解決僵尸進(jìn)程的問題)

    SIGCHLD信號產(chǎn)生的條件:

    • 子進(jìn)程終止時(shí);
    • 子進(jìn)程接收到 SIGSTOP 信號時(shí)變?yōu)橥V箲B(tài);
    • 子進(jìn)程處在停止態(tài),接受到SIGCONT后被喚醒時(shí);

    以上三種條件都會給父進(jìn)程發(fā)送 SIGCHLD 信號,父進(jìn)程默認(rèn)會忽略該信號。

    ☆☆☆使用SIGCHLD信號解決僵尸進(jìn)程的問題:
    示例:
    父進(jìn)程會執(zhí)行一個(gè)while死循環(huán),而子進(jìn)程輸出自己的pid就結(jié)束了,這樣子進(jìn)程會變成僵尸進(jìn)程,無法通過kill -9指令殺死,只能通過殺死父進(jìn)程或者讓父進(jìn)程循環(huán)調(diào)用wait()函數(shù)或者waitpid()函數(shù)來回收子進(jìn)程。
    但是我們希望有更好的解決方法:父進(jìn)程可以執(zhí)行它自己的內(nèi)容,而不用專門等待子進(jìn)程結(jié)束然后對其進(jìn)行回收,我們可以通過信號捕捉的方式,當(dāng)有子進(jìn)程結(jié)束時(shí)會向父進(jìn)程發(fā)送SIGCHLD信號,捕捉到這個(gè)信號時(shí)對其進(jìn)行回收。

    #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/wait.h>void myFun(int num) {printf("捕捉到的信號 :%d\n", num);// 回收子進(jìn)程PCB的資源:// while(1) {// wait(NULL); // }while(1) {int ret = waitpid(-1, NULL, WNOHANG);if(ret > 0) {printf("child die , pid = %d\n", ret);} else if(ret == 0) {// 說明還有子進(jìn)程活著//break;//要不要加這行?} else if(ret == -1) {// 沒有子進(jìn)程break;}} }int main() {// 提前設(shè)置好阻塞信號集,阻塞SIGCHLD,因?yàn)橛锌赡茏舆M(jìn)程很快結(jié)束,父進(jìn)程還沒有注冊完信號捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 創(chuàng)建一些子進(jìn)程pid_t pid;for(int i = 0; i < 8; i++) {pid = fork();if(pid == 0) {break;}}if(pid > 0) {// 父進(jìn)程// 捕捉子進(jìn)程死亡時(shí)發(fā)送的SIGCHLD信號struct sigaction act;act.sa_flags = 0;act.sa_handler = myFun;sigemptyset(&act.sa_mask);sigaction(SIGCHLD, &act, NULL);// 注冊完信號捕捉以后,解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);while(1) {printf("parent process pid : %d\n", getpid());sleep(2);}} else if( pid == 0) {// 子進(jìn)程printf("child process pid : %d\n", getpid());}return 0; }

    結(jié)果:

    child process pid : 2928710 child process pid : 2928711 child process pid : 2928712 child process pid : 2928713 捕捉到的信號 :17 child die , pid = 2928710 child die , pid = 2928711 child die , pid = 2928712 child die , pid = 2928713 child process pid : 2928714 child die , pid = 2928714 捕捉到的信號 :17 parent process pid : 2928701 parent process pid : 2928701 parent process pid : 2928701 parent process pid : 2928701

    4.5 多線程

    見Linux高并發(fā)服務(wù)器開發(fā)—筆記3

    總結(jié)

    以上是生活随笔為你收集整理的Linux高并发服务器开发---笔记2(多进程)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。