日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

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

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

0630

第4章 項目制作與技能提升

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

4.0 視頻課鏈接

4.1 項目介紹與環境搭建

4.2 Linux系統編程1、4.3 Linux系統編程2

4.4 多進程

1-9

Linux高并發服務器開發—筆記1

10.進程間通信☆☆☆

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

進程間通信的概念(IPC)

進程是一個獨立的資源分配單元,不同進程(這里所說的進程通常指的是用戶進程)之間的資源是獨立的,沒有關聯,不能在一個進程中直接訪問另一個進程的資源

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

進程間通信的目的:

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

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

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

①匿名管道(管道)— 親緣關系的進程

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

管道其實是一個在內核內存中維護的緩沖器,這個緩沖器的存儲能力是有限的,不同的操作系統大小不一定相同。
管道擁有文件的特質:讀操作、寫操作匿名管道沒有文件實體,有名管道有文件實體,但不存儲數據。可以按照操作文件的方式對管道進行操作。

一個管道是一個字節流,使用管道時不存在消息或者消息邊界的概念,從管道讀取數據的進程可以讀取任意大小的數據塊,而不管寫入進程寫入管道的數據塊的大小是多少。

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

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

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

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

創建匿名管道:

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

功能:創建一個匿名管道,用來進程間通信。
參數:int pipefd[2] 這個數組是一個傳出參數。

  • pipefd[0] 對應的是管道的讀端
  • pipefd[1] 對應的是管道的寫端

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

管道默認是阻塞的:如果管道中沒有數據,read阻塞;如果管道滿了,write阻塞

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

示例:

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

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

#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; }

結果:

pipe size : 4096

4096字節 = 4KB

示例:

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

編譯運行:

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)

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

    mkfifo 名字

    通過函數創建有名管道:

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

    參數:

    • pathname: 管道名稱的路徑
    • mode: 文件的權限, 和 open 的參數 mode 是一樣的,是一個八進制的數

    返回值:成功返回0,失敗返回-1,并設置錯誤號

    一旦使用 mkfifo 創建了一個 FIFO,就可以使用 open 打開它,常見的文件I/O 函數都可用于 fifo。如:close、read、write、unlink 等。
    FIFO 嚴格遵循先進先出(First in First out),對管道及 FIFO 的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如 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("管道不存在,創建管道\n"); ret = mkfifo("fifo1", 0664);//0664是權限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>// 從管道中讀取數據 int main() {// 1.打開管道文件int fd = open(" ", O_RDONLY);if(fd == -1) {perror("open");exit(0);}// 讀數據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);}// 寫數據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; }

    分別生成對應的以.o結尾的可執行文件,然后在兩個終端分別運行讀和寫:

    補充:

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

    補充:管道的讀寫特點

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

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

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

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

    總結:
    讀管道:

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

    寫管道:

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

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

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

    結果:

    ②內存映射 — mmap()函數

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

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

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

    使用內存映射實現進程間通信
    1.有關系的進程(父子進程)間通信
    還沒有子進程的時候,通過唯一的父進程,先創建內存映射區;
    有了內存映射區以后,創建子進程;
    父子進程
    共享
    創建的內存映射區

    2.沒有關系的進程間通信
    準備一個大小不是0的磁盤文件
    進程1 通過磁盤文件創建內存映射區,得到一個操作這塊內存的指針;
    進程2 通過磁盤文件創建內存映射區,得到一個操作這塊內存的指針;
    使用內存映射區通信;

    注意:內存映射區通信,是非阻塞

    ①mmap()函數:

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

    功能:將一個文件或者設備的數據映射到內存中
    參數:

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

    返回值:返回創建的內存的首地址;失敗返回MAP_FAILED,(void *) -1

    ②munmap()函數:

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

    功能:釋放內存映射
    參數:

    • addr : 要釋放的內存的首地址
    • length : 要釋放的內存的大小,要和mmap函數中的length參數的值一樣。

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

    匿名映射:不需要文件實體進程一個內存映射

    #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.創建匿名內存映射區int len = 4096;void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);//如果是匿名映射MAP_ANONYMOUS,那么倒數第二個參數fd就寫-1if(ptr == MAP_FAILED) {perror("mmap");exit(0);}// 父子進程間通信pid_t pid = fork();if(pid > 0) {// 父進程strcpy((char *) ptr, "hello, world");wait(NULL);}else if(pid == 0) {// 子進程sleep(1);printf("%s\n", (char *)ptr);}// 釋放內存映射區int ret = munmap(ptr, len);if(ret == -1) {perror("munmap");exit(0);}return 0; }

    示例2(有名映射)

    (共同映射到一個文件,如果這個文件不存在,就會返回錯誤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.打開一個文件int fd = open("test.txt", O_RDWR);int size = lseek(fd, 0, SEEK_END); // 獲取文件的大小// 2.創建內存映射區void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(ptr == MAP_FAILED) {perror("mmap");exit(0);}// 3.創建子進程pid_t pid = fork();if(pid > 0) {wait(NULL);// 父進程:讀數據char buf[64];strcpy(buf, (char *)ptr);printf("read data : %s\n", buf);}else if(pid == 0){// 子進程:寫數據strcpy((char *)ptr, "nihao a, son!!!");}// 關閉內存映射區munmap(ptr, size);return 0; }

    ③共享內存

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

    與管道等要求發送進程 將數據從用戶空間的緩沖區復制進內核內存接收進程將數據從內核內存復制進用戶空間的緩沖區 的做法相比,這種 IPC 技術的速度更快。

    使用步驟:

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

    共享內存相關的函數:

    頭文件:

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

    ①shmget()函數

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

    功能:創建一個新的共享內存段,或者獲取一個已有的共享內存段的標識。新創建的內存段中的數據都會被初始化為0
    參數:

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

    返回值:

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

    ②shmat()函數

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

    功能:和當前的進程進行關聯
    參數:

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

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

    ③shmdt()函數

    int shmdt(const void *shmaddr);

    功能:解除當前進程和共享內存的關聯
    參數:shmaddr,共享內存的首地址
    返回值:成功 0, 失敗 -1

    ④shmctl()函數

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

    功能:對共享內存進行操作刪除共享內存,共享內存要刪除才會消失,創建共享內存的進行被銷毀了對共享內存是沒有任何影響。
    參數:

    • shmid: 共享內存的ID
    • cmd : 要做的操作
      - IPC_STAT : 獲取共享內存的當前的狀態
      - IPC_SET : 設置共享內存的狀態
      - IPC_RMID: 標記共享內存被銷毀
    • buf:需要設置或者獲取的共享內存的屬性信息
      - IPC_STAT : buf存儲數據
      - IPC_SET : buf中需要初始化數據,設置到內核中
      - IPC_RMID : 沒有用,NULL

    ⑤ftok()函數

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

    功能:根據指定的路徑名,和int值,生成一個共享內存的key
    參數:

    • pathname:指定一個存在的路徑
      /home/nowcoder/Linux/a.txt
      /
    • proj_id: int類型的值,但是這系統調用只會使用其中的1個字節
      范圍 : 0-255 一般指定一個字符 ‘a’

    問題1:操作系統如何知道一塊共享內存被多少個進程關聯?

    共享內存維護了一個結構體struct shmid_ds 這個結構體中有一個成員 shm_nattch;
    shm_nattach 記錄了關聯的進程個數。

    問題2:可不可以對共享內存進行多次刪除 shmctl

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

    可以的,因為shmctl 標記刪除共享內存,不是直接刪除;

    那么什么時候真正刪除呢?
    和共享內存關聯的進程數為0的時候,就真正被刪除;
    當共享內存的key為0的時候,表示共享內存被標記刪除了;
    如果一個進程和共享內存取消關聯,那么這個進程就不能繼續操作這個共享內存,也不能進行關聯。

    ☆☆☆共享內存和內存映射的區別

    (參考mmap映射區和shm共享內存的區別總結)

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

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

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

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

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

    共享內存操作命令(打印當前系統中所有的 進程間通信方式 的信息)

    ipcs 用法:

    ipcs -a // 打印當前系統中所有的進程間通信方式的信息 ipcs -m // 打印出使用共享內存進行進程間通信的信息 ipcs -q // 打印出使用消息隊列進行進程間通信的信息 ipcs -s // 打印出使用信號進行進程間通信的信息

    ipcrm 用法:

    ipcrm -M shmkey // 移除用shmkey創建的共享內存段 ipcrm -m shmid // 移除用shmid標識的共享內存段 ipcrm -Q msgkey // 移除用msqkey創建的消息隊列 ipcrm -q msqid // 移除用msqid標識的消息隊列 ipcrm -S semkey // 移除用semkey創建的信號 ipcrm -s semid // 移除用semid標識的信號

    示例

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

    read_shm.c

    #include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h>int main() { // 1.獲取一個共享內存int shmid = shmget(100, 0, IPC_CREAT);printf("shmid : %d\n", shmid);// 2.和當前進程進行關聯void * ptr = shmat(shmid, NULL, 0);// 3.讀數據printf("%s\n", (char *)ptr);printf("按任意鍵繼續\n");getchar();// 4.解除關聯shmdt(ptr);// 5.刪除共享內存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.創建一個共享內存int shmid = shmget(100, 4096, IPC_CREAT|0664);printf("shmid : %d\n", shmid);// 2.和當前進程進行關聯void * ptr = shmat(shmid, NULL, 0);char * str = "helloworld";// 3.寫數據memcpy(ptr, str, strlen(str) + 1);printf("按任意鍵繼續\n");getchar();//按任意鍵// 4.解除關聯shmdt(ptr);// 5.刪除共享內存shmctl(shmid, IPC_RMID, NULL);return 0; }

    分別生成對應的可執行文件,用兩個終端分別打開。

    ④信號signal

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

    信號的基本概念

    信號是 Linux 進程間通信的最古老的方式之一,是事件發生時對進程的通知機制,有時也稱之為軟件中斷,它是在軟件層次上對中斷機制的一種模擬,是一種異步通信的方式。信號可以導致一個正在運行的進程被另一個正在運行的異步進程中斷,轉而處理某一個突發事件。

    發往進程的諸多信號,通常都是源于內核。引發內核為進程產生信號的各類事件如下

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

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

    使用信號的兩個主要目的是:

    • 讓進程知道已經發生了一個特定的事情;
    • 強迫進程執行它自己代碼中的信號處理程序。

    信號的特點:

    • 簡單;
    • 不能攜帶大量信息;
    • 滿足某個特定條件才發送;
    • 優先級比較高

    查看系統定義的信號列表:

    kill –l

    前 31 個信號為常規信號,其余為實時信號

    $ 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

    常用的信號:

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

    如果程序產生了段錯誤(Segment Fault),一般是因為訪問了非法內存,那么就可以通過生成core文件的方式來找到程序具體哪里出了問題;
    修改core文件大小
    編譯生成可執行程序時加上GDB選項;
    然后運行可執行程序時就會生成一個core文件;
    通過GDB調試,輸入core-file core,就可以看出來是哪里的代碼導致了段錯誤。

    core.c

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



    查看信號的詳細信息:

    man 7 signal

    信號的 5 種默認處理動作

    • Term 終止進程
    • Ign 當前進程忽略掉這個信號
    • Core 終止進程,并生成一個Core文件
    • Stop 暫停當前進程
    • Cont 繼續執行當前被暫停的進程

    信號的幾種狀態:產生、未決、遞達

    SIGKILLkill -9) 和 SIGSTOPkill -19) 信號不能被捕捉、阻塞或者忽略,只能執行默認動作。

    信號相關的函數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()函數:給任何的進程發送任何的信號 sig
    #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);

    功能:給任何的進程或者進程組pid, 發送任何的信號 sig
    參數:

    • pid :
      >0:將信號發送給指定的進程
      = 0 : 將信號發送給當前的進程組
      = -1 : 將信號發送給每一個有權限接收這個信號的進程
      < -1 : 這個pid=某個進程組的ID取反 (-12345)
    • sig : 需要發送的信號的編號或者是宏值,0表示不發送任何信號
    • 例如: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) {// 子進程int i = 0;for(i = 0; i < 5; i++) {printf("child process\n");sleep(1);}} else if(pid > 0) {// 父進程printf("parent process\n");sleep(6);printf("kill child process now\n");kill(pid, SIGINT);}return 0; }

    結果:

    child process parent process child process child process child process child process kill child process now
    ②raise()函數:給當前進程發送信號
    int raise(int sig);

    功能:給當前進程發送信號
    參數: sig : 要發送的信號
    返回值: 成功 0;失敗 非0
    相當于是kill(getpid(), sig);

    ③abort()函數:殺死當前進程
    void abort(void);

    功能: 發送SIGABRT信號給當前的進程,殺死當前進程
    相當于是kill(getpid(), SIGABRT);

    ☆☆☆④alarm()函數:設置定時器(鬧鐘),定時時間到了就終止當前的進程
    #include <unistd.h> unsigned int alarm(unsigned int seconds);

    功能:設置定時器(鬧鐘)。函數調用,開始倒計時,當倒計時為0的時候,函數會給當前的進程發送一個信號:SIGALARM,
    參數:seconds 倒計時的時長,單位:秒。如果參數為0,定時器無效(不進行倒計時,不發信號),就相當于是取消一個定時器,通過alarm(0)。

    返回值:

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

    信號SIGALARM :默認終止當前的進程,每一個進程都有且只有唯一的一個定時器。

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

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

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

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

    ⑤setitimer()函數:(既可以用來延時執行,也可定時執行;可以實現周期性定時)

    (見linux c setitimer用法說明)

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

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

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

    返回值:成功 0;失敗 -1,并設置錯誤號。

    結構體struct itimerval:

    // 定時器的結構體 struct itimerval { struct timeval it_interval; // 每個階段的時間,間隔時間struct timeval it_value; // 延遲多長時間執行定時器 };struct timeval { // 時間的結構體time_t tv_sec; // 秒數 suseconds_t tv_usec; // 微秒 };

    例如:it_value設定為10s,it_interval設定為2s,表示先倒計時10秒,然后循環倒計時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;} } //先倒計時5s,然后循環倒計時10s int main(int argc, char *argv[]) {//捕捉SIGALRM信號:signal(SIGALRM, signalHandler);struct itimerval new_value, old_value;//延遲的時間,5s之后開始第一次定時new_value.it_value.tv_sec = 5;new_value.it_value.tv_usec = 0;//間隔的時間:每次定時10snew_value.it_interval.tv_sec = 10;new_value.it_interval.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, &old_value);//printf("定時器開始了...\n");if(ret == -1) {perror("setitimer");exit(0);}for(int i = 0; i < 30; +i) {printf("%d\n", ++i);sleep(1);}return 0; }

    結果:先倒計時5s,然后循環倒計時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

    信號相關的函數2:(信號捕捉函數)signal()函數、sigaction()函數

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

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

    功能:設置某個信號的捕捉行為
    參數:

    • signum: 要捕捉的信號
    • handler: 捕捉到信號要如何處理
      ①SIG_IGN : 忽略信號
      ②SIG_DFL : 使用信號默認的行為
      ③回調函數handler : 這個函數是內核調用,程序員只負責寫,捕捉到信號后如何去處理信號。
      回調函數:
      - 需要程序員實現,提前準備好的,函數的類型根據實際需求,看函數指針的定義
      - 不是程序員調用,而是當信號產生,由內核調用
      - 函數指針是實現回調的手段,函數實現之后,將函數名放到函數指針的位置就可以了

    返回值:

    • 成功,返回上一次注冊的信號處理函數的地址。第一次調用返回NULL;
    • 失敗,返回SIG_ERR,設置錯誤號。

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

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

    ②sigaction()函數:

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

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

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

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

    結構體struct sigaction:

    struct sigaction {// 函數指針,指向的函數就是信號捕捉到之后的處理函數void (*sa_handler)(int);// 不常用void (*sa_sigaction)(int, siginfo_t *, void *);// 臨時阻塞信號集,在信號捕捉函數執行過程中,臨時阻塞某些信號。sigset_t sa_mask;// 使用哪一個信號處理對捕捉到的信號進行處理// 這個值可以是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秒鐘定時一次 int main() {struct sigaction act;act.sa_flags = 0;act.sa_handler = myalarm;//signalHandler;//sigemptyset(&act.sa_mask); // 清空臨時阻塞信號集// 注冊信號捕捉sigaction(SIGALRM, &act, NULL);struct itimerval new_value;// 設置間隔的時間new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 設置延遲的時間,3秒之后開始第一次定時new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的//printf("定時器開始了...\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; }

    結果:

    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

    許多信號相關的系統調用都需要能表示一組不同的信號,多個信號可使用一個稱之為信號集的數據結構來表示,其系統數據類型為 sigset_t。
    在 PCB 中有兩個非常重要的信號集。一個稱之為 “阻塞信號集” ,另一個稱之為 “未決信號集” 。這兩個信號集都是內核使用位圖機制來實現的,但操作系統不允許我們直接對這兩個信號集進行位操作,而需要自定義另外一個集合,借助信號集操作函數來對 PCB 中的這兩個信號集進行修改

    • 信號的 “未決” 是一種狀態,指的是從信號的產生信號被處理前的這一段時間;
    • 信號的 “阻塞” 是一個開關動作,指的是阻止信號被處理,但不是阻止信號產生。

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

    阻塞信號機 & 未決信號機:

  • 用戶通過鍵盤 Ctrl + C, 產生2號信號SIGINT (信號被創建)
  • 信號產生但是沒有被處理 (未決)
    • 在內核中將所有沒被處理的信號存儲在一個集合中; (未決信號集
    • SIGINT信號狀態被存儲在第二個標志位上;
      — 這個標志位的值為0, 說明信號不是未決狀態
      — 這個標志位的值為1, 說明信號處于未決狀態
  • 當某個未決狀態的信號需要被處理時,處理之前需要和另一個信號集(阻塞信號集)進行比較:
    • 阻塞信號集默認不阻塞任何的信號;
    • 如果想要阻塞某些信號需要用戶調用系統的API;(見下面的信號集相關操作函數
  • 在處理的時候和阻塞信號集中的標志位進行查詢,看是不是對該信號設置了阻塞
    • 如果沒有阻塞,這個信號就被處理;
    • 如果阻塞了,這個信號就繼續處于未決狀態,直到阻塞解除,這個信號才會被處理。
  • 信號集相關操作函數
    一、對自定義的信號集進行修改: 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二、對內核中的兩個信號集進行修改: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigpending(sigset_t *set);

    下面分別介紹一和二:

    一、對自定義的信號集進行修改

    以下信號集相關的函數都是對自定義的信號集進行操作
    ①sigemptyset()函數

    int sigemptyset(sigset_t *set);

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

    ②sigfillset()函數

    int sigfillset(sigset_t *set);

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

    ③sigaddset()函數

    int sigaddset(sigset_t *set, int signum);

    功能:設置信號集中的某一個信號對應的標志位為1,表示阻塞這個信號
    參數:

    • set:傳出參數,需要操作的信號集
    • signum:需要設置阻塞的那個信號

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

    ④sigdelset()函數

    int sigdelset(sigset_t *set, int signum);

    功能:設置信號集中的某一個信號對應的標志位為0,表示不阻塞這個信號
    參數:

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

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

    ⑤sigismember()函數

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

    功能:判斷某個信號是否阻塞,即它對應的標志位是否為1
    參數:

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

    返回值:

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

    ⑥示例:對自定義的信號集進行修改

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

    編譯運行:

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

    二、對內核中的兩個信號集進行修改
    sigprocmask()函數:

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

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

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

    返回值:成功:0;失敗:-1,設置錯誤號:EFAULT、EINVAL

    sigpending()函數:

    int sigpending(sigset_t *set);

    功能:獲取內核中的未決信號集
    參數:set,傳出參數,保存的是內核中的未決信號集中的信息

    ③示例:對內核中的兩個信號集進行修改

    // 編寫一個程序,把所有的常規信號(1-31)的未決狀態打印到屏幕 // 設置某些信號是阻塞的,通過鍵盤產生這些信號 #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h>int main() {// 設置2、3號信號阻塞sigset_t set;//自定義信號集sigemptyset(&set);//全部清零// 將2號和3號信號添加到信號集中sigaddset(&set, SIGINT);//Ctrl+csigaddset(&set, SIGQUIT);//Ctrl+\ // 修改內核中的阻塞信號集sigprocmask(SIG_BLOCK, &set, NULL);int num = 0;while(1) {num++;// 獲取當前的未決信號集的數據sigset_t pendingset;sigemptyset(&pendingset);//全部清零sigpending(&pendingset);//獲取內核中的未決信號集中的信息// 遍歷前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) {//循環10次// 解除阻塞,就只執行信號,而執行信號的結果就是結束進程sigprocmask(SIG_UNBLOCK, &set, NULL);}}return 0; }

    編譯運行:

    內核實現信號捕捉的過程

    補充:SIGCHLD信號(解決僵尸進程的問題)

    SIGCHLD信號產生的條件:

    • 子進程終止時;
    • 子進程接收到 SIGSTOP 信號時變為停止態;
    • 子進程處在停止態,接受到SIGCONT后被喚醒時;

    以上三種條件都會給父進程發送 SIGCHLD 信號,父進程默認會忽略該信號。

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

    #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);// 回收子進程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) {// 說明還有子進程活著//break;//要不要加這行?} else if(ret == -1) {// 沒有子進程break;}} }int main() {// 提前設置好阻塞信號集,阻塞SIGCHLD,因為有可能子進程很快結束,父進程還沒有注冊完信號捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 創建一些子進程pid_t pid;for(int i = 0; i < 8; i++) {pid = fork();if(pid == 0) {break;}}if(pid > 0) {// 父進程// 捕捉子進程死亡時發送的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) {// 子進程printf("child process pid : %d\n", getpid());}return 0; }

    結果:

    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高并發服務器開發—筆記3

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    91视频下载 | 国产麻豆视频网站 | 国产麻豆果冻传媒在线观看 | 国产精品一区二区三区免费看 | 久久超碰97 | 欧美激情综合五月色丁香 | 最新午夜电影 | 狠狠操欧美| 在线视频观看亚洲 | 在线观看免费成人 | 视频在线91 | 国产一区二区三区 在线 | 久久高清免费观看 | 97成人精品视频在线观看 | 激情欧美一区二区三区免费看 | 国产高潮久久 | 国产精品毛片一区视频 | 九九久久久久久久久激情 | 久久伦理电影 | 亚洲精品在线二区 | 日韩大片免费观看 | 久久首页| av再线观看| www.狠狠插.com | 日韩欧在线 | 欧美精品视 | 中文字幕有码在线播放 | 丝袜制服天堂 | 久久精品99国产精品日本 | 国产精品中文字幕在线观看 | 成人午夜免费福利 | 亚洲乱亚洲乱亚洲 | 97福利在线 | 久久九九精品久久 | 91精品国自产在线观看欧美 | 久久免费成人精品视频 | 精品国产乱码久久久久久1区二区 | 国产精品久久久久永久免费看 | 日本三级全黄少妇三2023 | 国产精品久久久毛片 | 欧洲精品码一区二区三区免费看 | 久久久久在线观看 | 九九热视频在线 | 久久免费观看视频 | 黄色大全在线观看 | 精油按摩av| 国际精品久久 | 久久精品视频国产 | 欧美综合在线视频 | 日韩在线免费不卡 | 精品五月天 | 国产九九热视频 | 日韩成人黄色av | 9免费视频 | 91片黄在线观看动漫 | 久久国色夜色精品国产 | 18国产精品白浆在线观看免费 | 国产精品免费观看久久 | 蜜臀av网址 | 中文av不卡| 国产精品久久久久久久久久了 | 亚洲欧美成人网 | 麻豆视频免费网站 | 国产精品美 | 人人看黄色 | 国产自产在线视频 | 亚洲午夜av| 五月天网站在线 | 91精品在线观看视频 | www.国产高清 | 中文字幕在线久一本久 | 97中文字幕 | 欧美成人影音 | 亚洲91网站| 国产精品久久久久久久久久久久午夜片 | 亚洲伦理精品 | 成人免费在线观看av | 亚洲欧美视频在线 | 国产99久 | 九九在线高清精品视频 | 一区二区激情视频 | 日本最新高清不卡中文字幕 | 久久久九色精品国产一区二区三区 | 中国一区二区视频 | 美女视频黄免费网站 | 亚洲理论在线观看电影 | 久久精品视频在线观看 | 91精品国产高清 | 婷婷六月中文字幕 | av大全在线免费观看 | 成人午夜电影久久影院 | 国产精品中文 | 操高跟美女 | 成人久久久久久久久久 | 精品视频在线观看 | 色网站在线免费 | 五月婷婷色综合 | av高清在线观看 | 国产剧在线观看片 | 亚洲精品视频在线观看网站 | 美女网站在线播放 | 97电影手机 | 欧美精品一区二区免费 | 久久久国产精品麻豆 | 91观看视频| 久草视频在线观 | 久久草视频 | 国产视频中文字幕在线观看 | 国产破处在线视频 | 黄色av网站在线免费观看 | 99亚洲精品视频 | 在线观看精品一区 | 日韩高清在线一区二区 | www黄色com| 91在线小视频 | 日韩欧美黄色网址 | 欧美日韩1区2区 | 免费亚洲电影 | 香蕉视频在线看 | 麻豆国产网站入口 | 欧美一区中文字幕 | 久久人人爽人人 | 狠狠操电影网 | 国产精品video | 91自拍视频在线 | 在线99热| 亚洲九九| 久久精品一二三 | 国产黄在线播放 | 成人黄色小说在线观看 | 亚洲国产剧情 | 在线国产中文字幕 | 久久久免费观看完整版 | 日日夜夜综合网 | 超碰在97| 99色网站| 国产成人精品一区二区三区网站观看 | av色影院| 视频1区2区| 8x成人免费视频 | 中文字幕在线免费观看 | 国产精品久久人 | 99精品在线视频观看 | 男女靠逼app | 国产一区久久久 | 日本精品久久久久影院 | 午夜视频久久久 | 免费看黄在线网站 | 精品国产乱码久久久久久1区2匹 | 国产不卡精品视频 | 欧美,日韩 | 激情文学综合丁香 | 色婷婷av国产精品 | 国产精品成人一区二区 | 国产一区网 | 日韩特级片 | 国产日本亚洲高清 | 99综合电影在线视频 | 欧美a级片免费看 | 一区二区高清在线 | 亚洲精品视频在线 | 四虎永久视频 | 亚洲欧美视频在线播放 | 国产伦精品一区二区三区照片91 | 婷婷去俺也去六月色 | 欧美性生交大片免网 | 亚洲日本在线视频观看 | 免费能看的黄色片 | 99热9| a级成人毛片| 高清av免费看 | 亚洲欧洲国产视频 | 免费在线观看视频一区 | 午夜av一区二区三区 | 国产精品久久久久久久久久三级 | 国产午夜精品久久久久久久久久 | 免费看污污视频的网站 | 日韩欧美一区二区在线播放 | 麻豆久久久久久久 | 免费中文字幕视频 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 日本黄色a级大片 | 免费a v视频 | 日韩欧美视频在线播放 | 91精品国产91热久久久做人人 | 国产精品精品国产色婷婷 | 日韩中文三级 | 黄a在线观看 | 99色99| 美女久久久久久久久久 | 国产精品免费一区二区三区在线观看 | 婷婷色综 | 天天综合天天做天天综合 | 911国产在线观看 | 99久久久久免费精品国产 | 国产精品99久久久精品 | 久草91视频 | 久久久高清一区二区三区 | 久久精品网站视频 | 国产视频一二区 | 丁香婷婷久久久综合精品国产 | 四虎免费av | 日韩高清一区 | 久久久久久久久久伊人 | 欧美久久久久久久久久 | 九九亚洲精品 | 免费a级大片 | 一二三精品视频 | 欧美日韩免费一区二区三区 | 欧美大码xxxx | 日韩三级中文字幕 | 日日操天天操夜夜操 | 婷婷夜夜 | 人人舔人人舔 | 欧美尹人 | 天天在线免费视频 | 五月婷婷丁香激情 | 国产日韩精品在线 | 91精品国产高清自在线观看 | 91免费国产在线观看 | 97网站| 久热超碰 | 久艹在线观看视频 | 久草在线高清 | 国产精品2020 | 草久久精品 | 亚洲一区视频在线播放 | 日本中文字幕一二区观 | 国产精品一区二区在线免费观看 | 热99在线视频| 国产日韩欧美在线看 | 天天激情 | 美女搞黄国产视频网站 | 超碰在线人| 美女国产 | 久久久久久久久久国产精品 | av黄色免费在线观看 | 久久综合给合久久狠狠色 | 欧美少妇xxxxxx | 久久99影院 | 日韩在线观看小视频 | 日日操网 | 91网页版免费观看 | 精品视频资源站 | 国产你懂的在线 | 精精国产xxxx视频在线播放 | av网站免费线看精品 | 色综合久久中文综合久久牛 | 国内外成人在线视频 | 国产黄免费看 | 99精品视频网站 | 成人在线网站观看 | 99久久成人| 免费日韩一区二区三区 | 激情综合网五月激情 | 国产精品九九久久久久久久 | 国产一区二区在线播放视频 | 黄色毛片在线观看 | 日本护士撒尿xxxx18 | 天天天色综合 | 国内精品中文字幕 | 91精品导航 | 国产黄色美女 | 黄色毛片大全 | 久久这里只精品 | 最近中文字幕mv免费高清在线 | 91麻豆看国产在线紧急地址 | 韩国精品一区二区三区六区色诱 | 日韩欧美在线视频一区二区 | 国产高清免费在线播放 | 99久久电影| 草久久久久久久 | 国产视频欧美视频 | 六月色婷 | 国产日韩在线播放 | 婷婷综合久久 | 国产免费观看久久 | 波多野结衣网址 | 日韩在线视频一区 | 色综合久久综合中文综合网 | 国产一卡二卡四卡国 | 日韩精品在线免费播放 | 一级a性色生活片久久毛片波多野 | 一级黄毛片| 麻豆视频在线观看免费 | 国产精品久久网站 | 日本在线成人 | 日本精品一区二区三区在线观看 | 97在线观看免费观看 | 亚洲欧美日本A∨在线观看 青青河边草观看完整版高清 | 亚洲精品在线观看视频 | 五月亚洲综合 | 久久久 精品 | 狠狠色网 | av久久在线| 9999精品视频 | 国产精品久久久久久久电影 | a黄色大片 | 狠狠伊人| 美女网站黄免费 | 亚洲做受高潮欧美裸体 | 亚洲精品久久久蜜桃直播 | 国产精品久久久久久久久久白浆 | 99视频国产精品 | 黄色的视频 | 久久成年视频 | 国产精品第 | 超级av在线| 伊人久久婷婷 | 日韩av网页| 久草剧场 | 亚洲成人精品av | 国产高清av免费在线观看 | 91精品久久香蕉国产线看观看 | 久久精品免费播放 | 成人久久久久久久久久 | 日本一区二区免费在线观看 | 九九九在线观看 | 欧美日韩精品在线播放 | 免费男女网站 | 久久九九久久九九 | 黄色成人在线观看 | 日韩一区二区三区高清免费看看 | 亚洲一区在线看 | 99夜色| 色.www | 国产a高清 | 天天草综合网 | 黄色字幕网 | 美女av电影 | 国产精品久久久久久久久免费看 | 亚洲毛片一区二区三区 | 亚洲精品99久久久久久 | 黄色国产在线 | 国产69久久| 久久九九影视 | 久久99偷拍视频 | 亚洲精品美女在线 | 欧美经典久久 | 91福利专区 | 超级碰碰碰免费视频 | 超碰在线网 | 91你懂的| 97超碰在线免费 | 久久免费视频7 | 97超碰国产精品女人人人爽 | 在线观看国产亚洲 | 人人插人人搞 | 成人黄色小说在线观看 | 欧美精品首页 | 国内小视频 | 黄色av电影在线观看 | 字幕网资源站中文字幕 | 久久99精品波多结衣一区 | 97视频人人免费看 | 91亚洲国产成人久久精品网站 | 国产精品黄网站在线观看 | 久草在线在线 | 国产亚洲人成网站在线观看 | 色噜噜日韩精品欧美一区二区 | 国产综合精品一区二区三区 | 欧美日韩国产亚洲乱码字幕 | 国产又黄又猛又粗 | 久久国产二区 | 免费在线观看av不卡 | 在线观看免费一区 | 国产剧在线观看片 | 久久久久久国产精品亚洲78 | 欧美一区二区三区免费看 | 中文字幕综合在线 | 五月婷婷开心中文字幕 | 日韩午夜三级 | 日韩精品视频在线观看网址 | 97免费公开视频 | 日韩国产欧美在线视频 | 久久人网 | 91久久人澡人人添人人爽欧美 | 亚洲国产精品va在线 | 97人人人人 | 亚洲国产精品久久 | 欧美在线1 | 国产美女被啪进深处喷白浆视频 | 免费看成人av | 九九免费精品视频 | 一区二区三区 亚洲 | av免费网站 | 97国产大学生情侣白嫩酒店 | 麻豆视频国产在线观看 | 丁香六月婷婷综合 | 99精品久久久久久久 | 黄色大全视频 | 香蕉视频色 | 成人黄色电影在线观看 | 国产成人精品一区二区三区网站观看 | 日韩在线视频一区 | 久久精品亚洲 | 亚洲精品乱码 | 欧美夫妻性生活电影 | 国产中文在线字幕 | 国产精品视频免费看 | 日韩性网站 | 成人免费视频网址 | 高清一区二区三区av | 免费在线看成人av | 久久九九影视网 | 精品国产一区二区三区久久影院 | 99草在线视频 | 成人va视频 | 国产一级91| 五月婷婷六月丁香在线观看 | 特级a毛片| 亚洲国产精品影院 | 在线观看中文字幕dvd播放 | 日日夜夜干 | 91在线免费播放 | 999久久久免费视频 午夜国产在线观看 | 久久综合成人 | 国产精品久久一卡二卡 | 国产又粗又长的视频 | 黄色免费看片网站 | 91精品国产成| 久久久国产精品成人免费 | 久久久久国产精品免费 | 亚洲精品视频免费看 | 免费视频成人 | 91精品啪 | 欧美日韩视频在线观看一区二区 | 国产视频精选在线 | 久草视频免费观 | 免费在线观看av网址 | 久久婷婷国产 | 91刺激视频 | 中文字幕人成不卡一区 | 91精品国产92久久久久 | 久久精品美女视频网站 | 91九色综合 | 免费的国产精品 | 国产黄色精品在线 | 在线有码中文 | 久久手机在线视频 | 综合网伊人 | 天天在线免费视频 | 99久久精品国产一区二区三区 | 色噜噜日韩精品一区二区三区视频 | 超碰免费久久 | 免费精品在线观看 | 亚洲成人一二三 | 色综合天天综合 | 国产精品一区在线播放 | av品善网 | 右手影院亚洲欧美 | 久久免费视频在线观看6 | 激情婷婷丁香 | 91麻豆精品国产自产在线 | 一级黄色在线免费观看 | 精品久久久久久久久久久久久久久久 | 亚洲国产97在线精品一区 | 国产无套精品久久久久久 | 日韩av女优视频 | 国产99色 | 色婷婷综合久久久中文字幕 | 午夜久久久久久久久久久 | 精品久久在线 | 国内精品二区 | 伊人网综合在线观看 | 国内精品久久久久久久影视麻豆 | 黄色av电影在线观看 | 亚洲最大av网站 | 操夜夜操| 精品女同一区二区三区在线观看 | 亚洲视频精选 | 伊人色综合久久天天网 | 91在线观看高清 | 亚洲h视频在线 | 99999精品视频 | 日韩 在线观看 | 夜夜操天天操 | 久久久久一区二区三区四区 | 伊人色综合网 | 中文字幕乱码亚洲精品一区 | 激情小说网站亚洲综合网 | 欧美精品九九99久久 | 久久99国产视频 | 欧美 亚洲 另类 激情 另类 | 日日干干夜夜 | 成人免费看视频 | 国产中文字幕视频在线 | 欧美一级免费黄色片 | 欧美在线视频一区二区三区 | 亚洲男男gaygay无套 | 日本久久久影视 | 午夜视频不卡 | 色wwww| 又粗又长又大又爽又黄少妇毛片 | 亚洲我射av | 日韩理论影院 | 亚洲日韩精品欧美一区二区 | 久久深夜福利免费观看 | 亚洲最大av | 亚洲激情在线观看 | 91av社区| 午夜av免费在线观看 | 亚洲国产日韩精品 | 91免费观看国产 | 91在线九色 | 91九色视频 | 日韩av在线高清 | 国产精品一区二区av | 999久久国精品免费观看网站 | 国产一区精品在线 | 精品国产一区二区三区在线观看 | 中文字幕在线免费观看 | 亚洲美女精品区人人人人 | 日韩欧美一区二区三区视频 | 美女福利视频 | 国产不卡在线 | 国产视频在线免费 | 蜜臀久久99精品久久久无需会员 | 四虎永久免费 | 久草视频视频在线播放 | 免费看三级黄色片 | 亚洲精品国精品久久99热 | 久久综合免费 | 99热精品国产一区二区在线观看 | 人人看看人人 | 不卡av在线免费观看 | www.狠狠插.com| av免费在线观看网站 | 亚洲精品美女久久久久 | 久久久久成人免费 | 国产精品wwwwww | 丰满少妇在线观看网站 | 91在线文字幕 | 免费在线91 | 国产免费又爽又刺激在线观看 | 国产区av在线 | 国产手机精品视频 | 偷拍区另类综合在线 | 大荫蒂欧美视频另类xxxx | 国产成人一区二区三区在线观看 | 色婷婷久久久综合中文字幕 | 日韩欧美在线一区二区 | 狠狠色丁香婷婷综合久小说久 | 久久久久 免费视频 | 蜜桃视频精品 | 五月婷亚洲 | 奇米影视999 | 99热官网| 国产精品久久久久久久久久久久久 | 五月综合激情婷婷 | 91高清视频 | 精品国偷自产国产一区 | 国产午夜影院 | 久久精品欧美视频 | 国产最新视频在线观看 | 午夜av在线 | 久久久www成人免费精品张筱雨 | 国产又粗又猛又黄视频 | 久久久影院一区二区三区 | 久久精品视频在线观看 | 国产91精品高清一区二区三区 | 干av在线 | 国产精品免费一区二区三区 | 日本黄色黄网站 | 久草精品在线观看 | 色狠狠一区二区 | 欧美一级性生活片 | 国产高清在线a视频大全 | 91在线一区 | 久久成人综合 | 91av视频免费在线观看 | 免费成人av在线 | 六月丁香在线观看 | 激情黄色av | 韩国av不卡 | 碰超人人 | 色狠狠婷婷 | 精品国产一区二区在线 | 久久艹在线观看 | 66av99精品福利视频在线 | 日韩一区正在播放 | 视频精品一区二区三区 | 五月天久久久久 | 99在线热播精品免费99热 | 久久久免费 | 色婷婷a| 91丨九色丨蝌蚪丰满 | 国产精品v欧美精品 | 欧美天天综合 | 久草在线看片 | 日本高清免费中文字幕 | 黄色一级网| 日韩精品一卡 | 国产啊v在线观看 | 狠狠久久| 久久免费国产精品 | 在线成人看片 | 欧美日韩国产一区二区三区 | 久久久久国产成人免费精品免费 | 久久精品三级 | 国产色中涩 | 99久久精品免费看国产麻豆 | 久久69精品 | 在线а√天堂中文官网 | 丰满少妇一级片 | 天天干干 | 日韩不卡高清视频 | 国产九九热视频 | 狠色狠色综合久久 | 欧洲亚洲精品 | 黄色网免费| 天天射天天干天天爽 | 中文字幕在线观看视频一区二区三区 | 激情六月婷婷久久 | 狠狠狠色丁香婷婷综合久久88 | 天天操天天干天天操天天干 | 久久在线免费 | 成人av电影在线播放 | 免费一级特黄录像 | 一级一片免费视频 | 亚洲黄色在线 | japanesefreesexvideo高潮| 久久精品一级片 | 成人午夜剧场在线观看 | 99视频99| 国产精品成人一区 | 超碰在线资源 | 亚洲日本一区二区在线 | 国产中文字幕国产 | 日韩精品一区二区三区在线视频 | 狠狠干狠狠艹 | 国产精品久久久久一区 | 中文字幕在线看视频 | 午夜精品999 | 久久久久久国产精品 | 日韩免费在线观看视频 | 黄色软件在线观看免费 | 免费看的黄网站软件 | 亚洲精品视频网站在线观看 | 久久久久久看片 | 狠狠色丁香久久婷婷综合五月 | 免费日韩一级片 | 最近中文字幕 | 久久久久久国产精品 | 久久草草热国产精品直播 | 国内精品久久久久影院一蜜桃 | av电影 一区二区 | 黄色精品久久 | 久久99精品久久久久久秒播蜜臀 | 精品亚洲免费 | 成人小视频在线播放 | 久草在线视频中文 | 亚洲黄色免费在线看 | 亚洲成成品网站 | 国产一区欧美日韩 | 欧美一性一交一乱 | 婷婷丁香在线视频 | 精油按摩av | 91av影视| 97精品一区| 亚洲视频国产 | 日韩精品一区二区在线观看视频 | 久久精品久久99 | 国产精品免费在线观看视频 | 久热av| 成人av在线亚洲 | 精品国产电影 | 日韩二区三区 | 成人国产精品久久久久久亚洲 | 中文字幕成人 | 久久久久综合 | 欧美精品久久久久久久久免 | 久久天天躁夜夜躁狠狠85麻豆 | 免费在线观看毛片网站 | 美女视频久久 | 色婷婷六月天 | 五月婷婷久久丁香 | 国产日韩精品一区二区在线观看播放 | 久久婷婷一区二区三区 | 精品一区二区在线免费观看 | 看av免费 | 日韩午夜一级片 | 深夜男人影院 | 成年人黄色免费看 | 日本亚洲国产 | 五月天综合网 | 亚洲人久久久 | 久久在线精品视频 | 99精品视频在线观看视频 | 欧美日韩国产精品一区二区三区 | 91高清视频 | 精品九九久久 | 亚洲自拍自偷 | 97人人超| 国产成人在线一区 | 亚洲欧美视屏 | 一区二区视频在线播放 | 色吧久久| av 一区二区三区 | 在线а√天堂中文官网 | 在线免费观看亚洲视频 | 欧美日韩aaaa | 91免费视频国产 | 国产免费资源 | 视频成人永久免费视频 | 亚洲精品午夜久久久久久久久久久 | 狠狠天天 | 婷婷色在线视频 | 五月婷婷中文网 | 国产精品视频免费看 | 国产视频在线播放 | 欧美特一级片 | 精品久久亚洲 | 999电影免费在线观看 | 91亚洲欧美激情 | 日日夜夜操操操操 | 91av在线免费观看 | 99久久久久久久 | 最新国产在线 | 国产馆在线播放 | 久久九九影院 | 欧美a性| 国产91大片| 97超视频| 久久香蕉一区 | 色综合婷婷久久 | 日韩国产精品久久 | 国产成人av一区二区三区在线观看 | 欧美精品一级视频 | 手机av网站| 欧美日韩xxx | 在线观看黄色大片 | 视频一区二区三区视频 | 久久都是精品 | 亚洲精品高清视频 | 亚洲欧美综合 | 中文字幕中文字幕中文字幕 | 久久人操 | 日本中文乱码卡一卡二新区 | 四虎影视成人永久免费观看视频 | .国产精品成人自产拍在线观看6 | 在线观看视频黄色 | 亚洲精品福利在线观看 | 日韩成人在线一区二区 | 在线免费视频你懂的 | 日本超碰在线 | 黄色片亚洲 | 婷婷丁香激情五月 | 日韩欧美精品在线视频 | 99亚洲精品 | 亚洲精品tv久久久久久久久久 | 91人人爽人人爽人人精88v | 精品久久亚洲 | 一区二区精品在线视频 | 亚洲va欧美| 黄色一级影院 | 亚洲综合成人av | 久久国产精品色婷婷 | 久草在线免费电影 | 国产精品黄色在线观看 | 国产精品久久一区二区三区不卡 | 日韩久久精品一区 | 国产精品久久久久婷婷二区次 | 久久免费av电影 | 久久视频在线观看中文字幕 | 在线看片91 | 亚洲精品一区中文字幕乱码 | 成人h动漫精品一区二 | 夜色成人网 | 欧洲精品码一区二区三区免费看 | 九九热免费精品视频 | 亚洲综合成人专区片 | 玖玖视频 | 在线日韩av | 久久成人亚洲欧美电影 | 国产黄色片久久久 | 日韩剧情| 日韩精品久久一区二区 | 99中文视频在线 | 91精品视频在线看 | 99日精品| 91精品国产九九九久久久亚洲 | 最近中文字幕免费 | 伊人成人激情 | 中文字幕一区二 | 欧美999| 色视频在线观看 | 精品一区二区三区香蕉蜜桃 | 91免费网址 | 99亚洲国产精品 | 久草精品资源 | 亚州成人av在线 | 日韩色爱| 国产精品久久久久9999 | 久久久久久美女 | 黄网av在线 | 国产精品色在线 | 日韩网站中文字幕 | 天天视频亚洲 | 天天玩天天干天天操 | 在线色视频小说 | 伊人色综合久久天天网 | 欧美国产精品一区二区 | 久久精品第一页 | 国产欧美最新羞羞视频在线观看 | 在线视频 精品 | 日韩a在线播放 | av理论电影 | 国产成人久久精品77777综合 | 一区二区av| 久久热首页 | 97电影网手机版 | 日韩欧美视频一区二区 | av在观看 | 91亚洲精品乱码久久久久久蜜桃 | 99久久毛片 | 久久网站免费 | 91在线视频观看 | 国产高清视频免费最新在线 | 久久精品国产精品 | 亚洲亚洲精品在线观看 | 青青网视频 | 免费福利小视频 | 亚洲成人高清在线 | 日韩专区av| 99久久夜色精品国产亚洲 | 国产精品伦一区二区三区视频 | 久久精品欧美日韩精品 | 国产一区二区中文字幕 | 色婷婷激情五月 | 色欧美成人精品a∨在线观看 | 久草免费手机视频 | 免费看黄色91 | 91精品久久香蕉国产线看观看 | 婷婷成人亚洲综合国产xv88 | 天天干夜夜夜 | 国产精品一区二区三区在线免费观看 | 97精品国产 | 日韩色中色 | 亚洲高清免费在线 | 丁香六月婷 | 国产精品久久一区二区无卡 | 亚洲精品久久久久久久不卡四虎 | 日本性视频 | 激情中文在线 | 亚洲另类视频在线观看 | 午夜av片| 在线电影 你懂得 | 色婷婷狠狠18 | 九九九九色| 中文字幕乱在线伦视频中文字幕乱码在线 | 蜜臀久久99精品久久久酒店新书 | www.夜夜操.com | 婷婷免费视频 | 国产美女黄网站免费 | 日韩亚洲国产中文字幕 | 婷婷在线免费 | 亚洲精品美女在线观看播放 | 99精品在这里 | 久久在线一区 | 久久人人97超碰国产公开结果 | 国产一级片不卡 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 91麻豆国产福利在线观看 | 亚洲第一久久久 | 在线观看免费中文字幕 | 在线观看中文字幕 | 精品视频久久 | 五月天天av | 在线观看免费视频你懂的 | 最近2019年日本中文免费字幕 | 亚洲精品在线视频观看 | 国产精品综合在线 | 久久亚洲综合色 | 欧美一级乱黄 | 五月婷婷综合激情网 | 久在线观看 | 久久精品久久精品久久精品 | 91最新网址在线观看 | 在线视频 你懂得 | 激情丁香久久 | av大片网址 | 国产精品成人一区二区 | 男女激情片在线观看 | 欧美日韩精品久久久 | 黄色免费视频在线观看 | 久久艹在线观看 | 久久久久激情视频 | 国产精品一区电影 | 日韩黄色网络 | 手机av在线网站 | 日韩在线视频二区 | 亚洲欧美乱综合图片区小说区 | 国产精品国产三级国产不产一地 | 欧美男女爱爱视频 | 亚洲成av人片在线观看香蕉 | 久久中文字幕视频 | 91污视频在线观看 | 久久精品5| av线上看 | a天堂最新版中文在线地址 久久99久久精品国产 | 天天爱天天射天天干天天 | 精品国产综合区久久久久久 | 欧美激情精品久久久久 | 天天激情站 | 奇米网网址 | 中文字幕国语官网在线视频 | 久久国产精品免费视频 | 日韩成人中文字幕 | 国产精品9区 | 97超碰在线人人 | 日韩一区在线播放 | av黄色免费在线观看 | 91日韩在线专区 | www.亚洲激情.com | 丁香综合av | 日狠狠| 色94色欧美 | 国产精品国产三级国产专区53 | 日韩精品不卡在线 | 不卡的av | 国产精品欧美久久久久天天影视 | 亚洲精品国产欧美在线观看 | 超碰97国产在线 | 99久久影院| 天天操天天操天天操天天操 | 亚洲国产字幕 | 中文字幕精品三区 | 99热日本 | 97超碰中文字幕 | 日日夜夜婷婷 | 天天操操操操操操 | av在线网站免费观看 | 中文字幕中文字幕 | 久久99精品一区二区三区三区 | 丁香六月国产 | 日韩精品第1页 | www.亚洲| 日韩综合一区二区 | 99久久精品国 | av夜夜操| 成年人黄色免费看 | 俺要去色综合狠狠 | 色999精品 | 久久久久久久久久久免费视频 | 国内精品久久久久久久久 | 色婷婷六月| 一级免费黄视频 | 亚洲精品国产综合99久久夜夜嗨 | 999久久久久久久久 69av视频在线观看 | 中文字幕视频播放 | 91资源在线播放 | 丁香亚洲 | 最近日本mv字幕免费观看 | 毛片在线播放网址 | 久久爱影视i | 久久久久在线 | 91av资源在线 | 久久99精品久久久久久清纯直播 | 香蕉视频国产在线观看 | 91九色视频国产 | 国产精品久久久久久久午夜片 | 日韩极品视频在线观看 | 欧美日韩3p | 婷婷六月在线 | 99视频99| 欧美日韩不卡一区二区 | 亚洲精品色视频 | 高清中文字幕 | 超碰97人人干 | 久久久久日本精品一区二区三区 | 日日碰夜夜爽 | 91av短视频| 狠狠搞,com | 久章草在线观看 | 久久色视频 | 免费网站看av片 | 日韩精品无码一区二区三区 | 久草精品视频 | 亚洲日本色 | 在线观看日韩视频 | 中文字幕在线资源 | 中文字幕乱码日本亚洲一区二区 | 91一区二区三区久久久久国产乱 | 91免费高清观看 | 狠狠干夜夜操 | 久久综合色播五月 | 欧美一区二区三区免费观看 | 久久免费视频网 | 99麻豆久久久国产精品免费 | 国产不卡精品 | 欧美一级乱黄 | 亚洲欧美激情精品一区二区 | 久久国产福利 | 日日爱影视 | 国产日产av | 99久久久久久国产精品 | 日韩欧美国产精品 |