1. 程式人生 > 其它 >node 儲存過程_MariaDB Galera Cluster 搭建過程

node 儲存過程_MariaDB Galera Cluster 搭建過程

技術標籤:Linuxlinuxc語言

文章目錄

Linux檔案開發

檢視Linux使用者手冊

man + 關鍵字
man 2 open開啟使用者手冊中的第2頁的open函式那一章

檔案的開啟與建立open函式

包含標頭檔案

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YqgsKKBe-1610010410731)(en-resource://database/589:1)]
file descriptor檔案描述符
call呼叫
給一個檔案的路徑,返回一個檔案描述符,它是一個小的非負整數用在後續的系統呼叫中。檔案開啟成功返回非負整數,開啟失敗返回-1
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wFdIIwog-1610010410737)(en-resource://database/591:1)]

檢視檔案許可權

ls -lrw可讀寫;x可執行
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fFk6DOI9-1610010410740)(en-resource://database/593:1)]
open函式中的mode許可權:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-VFB7JNr9-1610010410743)(en-resource://database/595:1)]
可讀可寫就是0400+0200=0600

用法

  1. 正常開啟一個檔案:新建一個file1檔案,在當前資料夾內新建file1.c輸入如下程式碼
  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR);
 10     printf("fd = %d\n",fd);
 11     return 0;
 12 }
//fd = 3
  1. 如果檔案不存在則建立檔案並開啟:刪除file1檔案,在當前資料夾內新建file1.c輸入如下程式碼
 1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR);
 10     if(fd = -1)
 11     {
 12         printf("file doesn't exist!\n");
 13         fd = open("./file1",O_RDWR|O_CREAT,00600);
 14         if(fd > 0)
 15         {
 16             printf("create file successfully!\n");
 17         }
 18     }
 19     printf("fd = %d\n",fd);
 20     return 0;
 21 }
//file doesn't exist!
//create file successfully!
//fd = 3

檔案寫入操作程式設計write函式

write函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YRyirbzW-1610010410745)(en-resource://database/597:1)]
write函式從buf緩衝區向檔案描述符fd指代的檔案中寫入count大小的資料。
如果寫入成功,返回寫入資料的大小(0代表沒東西被寫入),-1表示有錯誤。

包含標頭檔案

#include <unistd.h>

close函式

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YAtt90lC-1610010410748)(en-resource://database/599:1)]
寫入檔案內容後用close函式關閉檔案

用法

如果file1檔案不存在則先建立,再寫入12345678901234567890,最後關閉檔案file1

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR);
 12     if(fd = -1)
 13     {
 14         printf("file doesn't exist!\n");
 15         fd = open("./file1",O_RDWR|O_CREAT,00600);
 16         if(fd > 0)
 17         {
 18             printf("create file successfully!\n");
 19         }
 20     }
 21     printf("open file successfully!\n",fd);
 22     char *buf = "12345678901234567890";
 23     write(fd,buf,strlen(buf)*sizeof(char));
 24     close(fd);
 25     return 0;
 26 }

檔案讀取操作read函式

函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7nHDvQNw-1610010410749)(en-resource://database/601:1)]
函式會從檔案描述符fd所在的檔案中讀取count個位元組大小的資料到buf緩衝區內。
讀取成功返回讀取的位元組大小,失敗返回-1

包含標頭檔案

#include <unistd.h>

用法

讀取寫入file1檔案的資料,讀取時採用重新開啟檔案讓游標定位到資料開頭

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdlib.h>
  8 
  9 int main()
 10 {
 11     int fd;
 12     fd = open("./file1",O_RDWR);
 13     if(fd = -1)
 14     {
 15         printf("file doesn't exist!\n");
 16         fd = open("./file1",O_RDWR|O_CREAT,00600);
 17         if(fd > 0)
 18         {
 19             printf("create file successfully!\n");
 20         }
 21     }
 22     printf("open file successfully!\n",fd);
 23     char *buf = "12345678901234567890";
 24     int n_write;
 25     n_write = write(fd,buf,strlen(buf)*sizeof(char));
 26     close(fd);
 27     fd = open("./file1",O_RDWR|O_CREAT,00600);
 28     if(n_write != -1)
 29     {
 30         printf("write %d bytes to file1\n",n_write);
 31     }
 32     int n_read;
 33     char *readBuf = (char *)malloc(n_write);
 34     n_read = read(fd,readBuf,n_write);
 35     printf("read %d bytes,context is:%s\n",n_read,readBuf);
 36     return 0;
 37 }

檔案游標移動操作lseek函式

函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IBl9RKx9-1610010410750)(en-resource://database/603:1)]
函式是用來重新定位開啟的檔案的游標的。
SEEK_SET游標在資料頭部位置
SEEK_CUR游標在資料當前位置
SEEK_END游標在資料尾部位置
offset設定游標向右偏移位元組大小,向左偏移為負數
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-gQVUxIKm-1610010410751)(en-resource://database/605:1)]
讀取成功返回從檔案開始的偏移位元組大小,讀取失敗返回-1

包含標頭檔案

#include <sys/types.h>
#include <unistd.h>

用法

  1. 開啟檔案並寫入資料後,將游標重新定位至資料頭部,讀取資料
  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 #include <stdlib.h>
  8 
  9 int main()
 10 {
 11     int fd;
 12     fd = open("./file1",O_RDWR);
 13     if(fd = -1)
 14     {
 15         printf("file doesn't exist!\n");
 16         fd = open("./file1",O_RDWR|O_CREAT,00600);
 17         if(fd > 0)
 18         {
 19             printf("create file successfully!\n");
 20         }
 21     }
 22     printf("open file successfully!\n",fd);
 23     char *buf = "12345678901234567890";
 24     int n_write;
 25     n_write = write(fd,buf,strlen(buf)*sizeof(char));
 26     if(n_write != -1)
 27     {
 28         printf("write %d bytes to file1\n",n_write);
 29     }
 30     int n_read;
 31     char *readBuf = (char *)malloc(n_write);
 32     lseek(fd,-n_write,SEEK_END);
 //lseek(fd,0,SEEK_SET); //lseek(fd,-n_write,SEEK_CUR);都可以
 33     n_read = read(fd,readBuf,n_write);
 34     printf("read %d bytes,context is:%s\n",n_read,readBuf);
 35     return 0;
 36 }
  1. 計算檔案大小
1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <sys/types.h>
  5 #include <unistd.h>
  6 #include <stdio.h>
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR);
 12     if(fd == -1)
 13     {
 14         printf("file1 doesn't exist!\n");
 15         fd = open("./file1",O_RDWR|O_CREAT,00600);
 16         printf("create file1 success!\n");
 17     }
 18     printf("open file1 success!");
 19     int fileSize;
 20     fileSize = lseek(fd,0,SEEK_END);
 21     printf("file1's size is %d\n",fileSize);
 22     return 0;
 23 }

因為lseek返回的是從檔案開始到游標的偏移位元組大小,所以要把游標移動到檔案尾部lseek(fd,0,SEEK_END)

檔案開啟建立補充

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZtXmFBF8-1610010410753)(en-resource://database/607:1)]

O_EXCL引數描述

如果在open函式內同時寫了O_CREAT,如果檔案存在則開啟失敗返回-1

O_EXCL引數的用法

如果檔案file1存在則提示檔案已存在,如果刪除file1重新呼叫程式,則建立檔案

1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 
  6 int main()
  7 {
  8     int fd;
  9     fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,00600);
 10     if(fd == -1)
 11     {
 12         printf("file1 has existed!\n");
 13     }
 14     return 0;
 15 }

O_APPEND引數描述

寫入資料時如果首次呼叫含O_APPEND的open函式則會在檔案尾部寫入資料

O_APPEND引數的用法

如果用了該引數則在檔案尾部寫資料,如果沒用該引數,寫入的新資料會覆蓋原來的位置

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR|O_APPEND);
 12     printf("fd = %d\n",fd);
 13     char *buf = "peterpeter";
 14     write(fd,buf,strlen(buf)*sizeof(char));
 15     close(fd);
 16     return 0;
 17 }

O_TRUNC引數描述

如果檔案中有內容,將其清空再寫入新資料。
注意:在open函式里加了該引數,後面就讀取不到內容了,因為被清空了

O_TRUNC引數的用法

 1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <string.h>
  7 
  8 int main()
  9 {
 10     int fd;
 11     fd = open("./file1",O_RDWR|O_TRUNC);
 12     printf("fd = %d\n",fd);
 13     char *buf = "peterpeter";
 14     write(fd,buf,strlen(buf)*sizeof(char));
 15     close(fd);
 16     return 0;
 17 }

creat函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-SjCLjT45-1610010410754)(en-resource://database/609:1)]

creat函式的用法

在當前路徑下建立一個名為file2的檔案,可讀可寫可執行

 1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 
  5 int main()
  6 {
  7     creat("./file2",S_IRWXU);
  8     return 0;
  9 }

檔案操作原理簡述

檔案描述符

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Mgpcfcll-1610010410755)(en-resource://database/611:1)]

標準輸入和標準輸出

檔案描述符(0,1,2)巨集STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO代表標準輸入、標準輸出和標準錯誤。標準輸入就是從鍵盤讀取;標準輸出就是從控制檯輸出

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7     char *readBuf = (char *)malloc(10);
  8     read(STDIN_FILENO,readBuf,10);
  9     write(STDOUT_FILENO,readBuf,10);
 10     printf("done!\n");
 11     return 0;
 12 }   
從鍵盤輸入//sdfs
//sdfs
//done!

從鍵盤讀取10個位元組到readBuf緩衝區,再從readBuf緩衝區寫到控制檯10個位元組,所以這裡都是readBuf

操作檔案的步驟

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-sfsvBubV-1610010410756)(en-resource://database/612:1)]
靜態檔案存在塊裝置中 -> 呼叫open -> 在linux核心申請記憶體(動態記憶體)用一個數據結構儲存 -> 讀寫 -> 呼叫close -> linux核心中的動態記憶體更新塊裝置中的靜態檔案

檔案操作應用——實現cp命令

main函式的引數

main函式的引數從第一個指令開始

  1 #include <stdio.h>
  2 
  3 int main(int argc,char **argv)
  4 {
  5     printf("number of argc is:%d\n",argc);
  6     printf("No.1 param is:%s\n",argv[0]);
  7     printf("No.2 param is:%s\n",argv[1]);
  8     printf("No.3 param is:%s\n",argv[2]);
  9     return 0;
 10 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IadMOCp7-1610010410758)(en-resource://database/613:1)]

程式設計實現

思路:
開啟src.c
讀src.c中的資料到readBuf
開啟/建立des.c
將readBuf寫入des.c,如果des.c不存在則建立,如果des.c存在,寫入前清空其內容
關閉兩個檔案

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdlib.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 
  9 int main(int argc,char **argv)
 10 {
 11     if(argc != 3)
 12     {
 13         printf("param error!\n");
 14         exit(-1);
 15     }
 16     int fdSrc;
 17     fdSrc = open(argv[1],O_RDWR);
 18     int fileSize = lseek(fdSrc,0,SEEK_END);
 19     lseek(fdSrc,0,SEEK_SET);
 20     char *readBuf = (char *)malloc(sizeof(char)*fileSize);
 21     read(fdSrc,readBuf,fileSize);
 22     int fdDes;
 23     fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,00600);
 24     write(fdDes,readBuf,strlen(readBuf)*sizeof(char));
 25     close(fdSrc);
 26     close(fdDes);
 27     return 0;
 28 }
 //gcc mycp.c -o mycp
//./mycp file1 file2
//file1被複製成file2

perror()函式列印錯誤資訊

呼叫perror函式後程序會自動結束

檔案程式設計應用——修改程式的配置檔案

思路:
開啟檔案
讀取檔案到readBuf緩衝區
用strstr()查詢目標字串
指標定位到目標資料位置並修改資料
將readBuf寫入原檔案
關閉檔案
以下程式碼只能修改1位數字

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdlib.h>
  6 #include <string.h>
  7 #include <stdio.h>
  8 
  9 int main(int argc,char **argv)
 10 {
 11     if(argc != 2)
 12     {
 13         printf("param error!\n");
 14         exit(-1);
 15     }
 16     int fd;
 17     fd = open(argv[1],O_RDWR);
 18     int fileSize = lseek(fd,0,SEEK_END);
 19     lseek(fd,0,SEEK_SET);
 20     char *readBuf = (char *)malloc(sizeof(char)*fileSize + 1);
 21     read(fd,readBuf,fileSize);
 22     char *p = strstr(readBuf,"WIDTH = ");
 23     if(p == NULL)
 24     {
 25         printf("not found!\n");
 26         exit(-1);
 27     }
 28     p = p + strlen("WIDTH = ");
 29     *p = '8';
 30 /*  while(++p != NULL)
 31     {
 32         if(*p == '\0')
 33         {
 34             break;
 35         }
 36         *p = '\0';
 37     }*/
 38     lseek(fd,0,SEEK_SET);
 39     write(fd,readBuf,fileSize);
 40     close(fd);
 41     return 0;
 42 }
 //gcc changeConfig.c -o changeConfig
 //./changeConfig test.config
 WIDTH修改為8

寫除了字串型別到檔案

寫一個整數到檔案

write()和read()函式中的引數型別是const void * 所以不一定要是char * 型別

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdio.h>
  6 
  7 int main()
  8 {
  9     int fd;
 10     fd = open("./file1",O_RDWR);
 11     int data = 10;
 12     write(fd,&data,sizeof(int));
 13     lseek(fd,0,SEEK_SET);
 14     int data2;
 15     read(fd,&data2,sizeof(int));
 16     printf("read:%d\n",data2);
 17     close(fd);
 18     return 0;
 19 }

寫一個結構體到檔案

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdio.h>
  6 
  7 typedef struct node
  8 {
  9     int a;
 10     char c;
 11 }Node;
 12 
 13 int main()
 14 {
 15     int fd;
 16     fd = open("./file1",O_RDWR);
 17     Node node1 = {1,'g'};
 18     write(fd,&node1,sizeof(Node));
 19     lseek(fd,0,SEEK_SET);
 20     Node node2;
 21     read(fd,&node2,sizeof(Node));
 22     printf("read:%d,%c\n",node2.a,node2.c);
 23     close(fd);
 24     return 0;
 25 }

寫一個結構體陣列到檔案

  1 #include <sys/types.h>
  2 #include <sys/stat.h>
  3 #include <fcntl.h>
  4 #include <unistd.h>
  5 #include <stdio.h>
  6 
  7 typedef struct node
  8 {
  9     int a;
 10     char c;
 11 }Node;
 12 
 13 int main()
 14 {
 15     int fd;
 16     fd = open("./file1",O_RDWR);
 17     Node node1[2] = {{1,'g'},{2,'h'}};
 18     write(fd,&node1,sizeof(Node)*2);
 19     lseek(fd,0,SEEK_SET);
 20     Node node2[2];
 21     read(fd,&node2,sizeof(Node)*2);
 22     printf("read:%d,%c\n",node2[0].a,node2[0].c);
 23     printf("read:%d,%c\n",node2[1].a,node2[1].c);
 24     close(fd);
 25     return 0;
 26 }

open和fopen的區別

總結open與fopen的區別

  1. 來源不同
    open是Unix系統呼叫函式。fopen的C語言庫函式。

  2. 移植性不同
    fopen移植性更好,能在更多系統中呼叫。

  3. 適用範圍不同
    fopen用來操作普通檔案。open主要用於linux系統。

  4. 緩衝
    fopen是在緩衝區操作檔案,效率更高。open是通過使用者態和核心態切換操作檔案。但現在的機器這點效率忽略不計。

標準C庫操作檔案

fopen()函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fXtmd8DI-1610010410760)(en-resource://database/615:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-aOyDSKpz-1610010410761)(en-resource://database/617:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-VA3eYtRZ-1610010410762)(en-resource://database/619:1)]
fopen()函式用來開啟檔案。傳入path檔案路徑,mode開啟的檔案許可權,mode是字串指標 。許可權如第二張圖,w+表示不存在就建立檔案。成功呼叫該函式返回FILE的指標,有錯返回NULL

fwrite()和fread()函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-acABIAQV-1610010410763)(en-resource://database/621:1)]
fwrite()函式用來向檔案寫入資料。它從ptr緩衝區獲取nmemb數量每個size位元組大小的資料寫入stream指向的檔案流。

fread()函式用來讀取資料。它從stream指向的檔案流中讀取nmemb數量每個size位元組大小的資料儲存到ptr緩衝區中。

fwrite()和fread()返回成功寫入或讀取的專案數量,如果調用出錯返回很小的專案數量或0。它們的返回值取決於第三個引數nmemb
如果fwrite的nmemb引數寫的很大,會將亂碼寫入目標檔案,如下圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-02Mo79Hb-1610010410765)(en-resource://database/639:1)]
如果fread的nmemb引數寫的很大,沒有問題

fseek()函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oGuKqI5z-1610010410766)(en-resource://database/623:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YnpZZf9b-1610010410767)(en-resource://database/625:1)]
fseek用來操作檔案中的游標位置。引數和lseek用法相同。成功呼叫返回0,失敗返回-1。

用法

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 
  5 int main()
  6 {
  7     FILE *fp;
  8     fp = fopen("./file1","w+");
  9     char *str = "hello world!";
 10     int n_write = fwrite(str,sizeof(char),strlen(str),fp);
 11     fseek(fp,0,SEEK_SET);
 12     char *readBuf = (char *)malloc(20);
 13     int n_read = fread(readBuf,sizeof(char),strlen(str),fp);
 14     printf("read:%s\n",readBuf);
 15     printf("n_write = %d\n",n_write);
 16     printf("n_read = %d\n",n_read);
 17     return 0;
 18 }
//read:hello world!
//n_write = 12
//n_read = 12

如果寫入的資料數量大於字元數量,比如fwrite(str,sizeof(char),20,fp);會在file1中多寫入其它字元造成錯誤

寫入結構體

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 
  5 typedef struct Node
  6 {
  7     int num;
  8     char name;
  9 }Node;
 10 int main()
 11 {
 12     Node data1 = {100,'a'};
 13     FILE *fp;
 14     fp = fopen("./file1","w+");
 15     int nwrite = fwrite(&data1,sizeof(Node),1,fp);
 16     Node data2;
 17     fseek(fp,0,SEEK_SET);
 18     int nread = fread(&data2,sizeof(Node),1,fp);
 19     printf("read:num = %d\tname = %c\n",data2.num,data2.name);
 20     printf("write:%d\tread:%d\n",nwrite,nread);
 21     fclose(fp);
 22     return 0;
 23 }
//read:num = 100    name = a
//write:1   read:1

fputc()函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7eRla7e6-1610010410769)(en-resource://database/641:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Jv5Hr3Eo-1610010410770)(en-resource://database/643:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cJ9nmYz6-1610010410771)(en-resource://database/645:1)]
fputc()函式將引數c強制轉換成無符號字元,寫入stream檔案流
返回被寫入的無符號字元強轉成整型的整數,失敗返回EOF

用法

將字串寫入file1檔案中

  1 #include <stdio.h>
  2 #include <string.h>
  3 
  4 int main()
  5 {
  6     FILE *fp;
  7     fp = fopen("./file1","w+");
  8     char *str = "hello world!";
  9     int i;
 10     int len = strlen(str);
 11     int a;
 12     for(i = 0; i < len; i++)
 13     {
 14         a = fputc(*str,fp);
 15         str++;
 16         printf("fputc = %d\n",a);
 17     }
 18     fclose(fp);
 19     return 0;
 20 }
注意這裡for迴圈中要將len提前賦值,因為如果寫成i < strlen(str),隨著每次迴圈str++,strlen(str)的值也會隨之變化

fgetc()和feof()函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-2mRAgVj8-1610010410772)(en-resource://database/649:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-e45Li0rU-1610010410773)(en-resource://database/651:1)]
fgetc()函式從stream檔案流中讀取一個字元,將其轉換成無符號字元並強轉成整型輸出,返回EOF時有錯誤
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-eqaG7n7o-1610010410775)(en-resource://database/653:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rUzZfhOD-1610010410776)(en-resource://database/655:1)]
feof()函式判斷游標是否在檔案尾,如果游標在檔案尾部返回非零數,如果不在返回0

用法

  1 #include <stdio.h>
  2 #include <string.h>
  3 
  4 int main()
  5 {
  6     FILE *fp;
  7     fp = fopen("./file1","r");
  8     char c;
  9     while(!feof(fp))
 10     {
 11         c = fgetc(fp);
 12         printf("%c",c);
 13     }   
 14     fclose(fp);
 15     return 0;
 16 }   
//hello world!

Linux程序

程序的相關概念

什麼是程式,什麼是程序,有什麼區別?

程式是靜態的概念,gcc xxx.c -o pro,生成的pro就是程式,各種桌面應用程式沒開啟前都是程式。
程序是程式的一次執行活動,就是程式跑起來了,系統中就多了一個程序。

如何檢視系統中有哪些程序?

在Windows中通過工作管理員檢視程序。

在Linux中使用ps指令檢視,ps -aux檢視所有程序,ps -aux|grep init在所有程序中檢視帶init欄位的程序。
top指令檢視程序排名,可以檢視cpu消耗率,記憶體佔有率等。

什麼是程序識別符號?

每個程序都有一個非負整數表示唯一ID,叫做pid,類似身份證。
pid = 0:稱為交換程序(swapper)作用是程序排程
pid = 1:init程序,作用是系統初始化
程式設計呼叫getpid()函式能獲取自身的程序識別符號,getppid獲取父程序的程序識別符號。

 1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid = getpid();
  9     printf("my pid = %d\n",pid);
 10     while(1);
 11     return 0;
 12 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0m7X8Wuo-1610010410777)(en-resource://database/657:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-W1GE1g1r-1610010410778)(en-resource://database/659:1)]

什麼叫父程序,什麼叫子程序?

如果程序A建立了程序B,A叫做父程序,B叫做子程序。

C程式的儲存空間是如何分配的?

程式碼段:演算法類的程式碼
資料段:初始化過的變數
bss()段:在函式外未被初始化的變數
堆:calloc或malloc申請的記憶體
棧:區域性變數儲存的地方
命令列引數和環境變數:argc和argv存放的地方
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-FjmrzcCI-1610010410780)(en-resource://database/661:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7796BfEi-1610010410781)(en-resource://database/663:1)]

建立程序fork()函式

函式描述

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1bSPD8vS-1610010410782)(en-resource://database/665:1)]
fork()函式以及後面的程式碼會在父程序中執行一次,在子程序中再執行一次;當fork()函式返回0代表當前是子程序,返回非負數(建立的子程序ID)代表當前是父程序,呼叫失敗返回-1

用法

fork()函式以及後面的程式碼會在父程序中執行一次,在子程序中再執行一次

1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid = getpid();
  9     printf("before fork,pid = %d\n",pid);
 10     fork();
 11     printf("after fork,pid = %d\n",getpid());
 12     return 0;
 13 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lv75CapE-1610010410784)(en-resource://database/667:1)]
當fork()函式返回0代表當前是子程序,返回非負數代表當前是父程序

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid_t forkReturn;
  9     pid = getpid();
 10     printf("before fork,pid = %d\n",pid);
 11     forkReturn = fork();
 12     printf("after fork,pid = %d\n",getpid());
 13     if(forkReturn > 0)
 14     {
 15         printf("fork return:%d\tthis is father fork,pid = %d\n",forkReturn,getpid());
 16     }
 17     else if(forkReturn == 0)
 18     {
 19         printf("fork return:%d\tthis is child fork,pid = %d\n",forkReturn,getpid());
 20     }
 21     else
 22     {
 23         printf("error\n");
 24     }
 25     return 0;
 26 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7VFomnpK-1610010410785)(en-resource://database/668:1)]

程序建立發生了什麼

呼叫fork()函式後最早期的Linux核心進行了全拷貝:把父程序的正文、堆、棧等記憶體空間全部拷貝了一份給子程序
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9taGpbh1-1610010410786)(en-resource://database/669:1)]
後來Linux發展, 用的是寫時拷貝,只對變化的部分進行拷貝

如下例:只對變數data的值進行了拷貝

 1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main()
  6 {
  7     pid_t pid;
  8     pid_t forkReturn;
  9     pid = getpid();
 10     int data = 10;
 11     printf("before fork,pid = %d\n",pid);
 12     forkReturn = fork();
 13     printf("after fork,pid = %d\n",getpid());
 14     if(forkReturn > 0)
 15     {
 16         printf("fork return:%d,this is father fork,pid = %d,data = %d\n",forkReturn,getpid(),data);
 17     }
 18     else if(forkReturn == 0)
 19     {
 20         data += 10;
 21         printf("fork return:%d,this is child fork,pid = %d,data = %d\n",forkReturn,getpid(),data);
 22     }
 23     else
 24     {
 25         printf("error\n");
 26     }
 27     return 0;
 28 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zayYWNV3-1610010410788)(en-resource://database/670:1)]

建立新程序的實際運用場景

目的一

讓父、子程序同時執行不同的。在網路服務程序中是常見的——父程序等待客戶端的服務請求。當這種請求到達時,父程序呼叫fork,使子程序處理此請求。父程序繼續等待下一個服務請求到達。

如下例:用data==1時模擬請求到達時

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main()
  6 {
  7     pid_t forkReturn;
  8     int data = 0;
  9     while(1)
 10     {
 11         printf("Please input data:");
 12         scanf("%d",&data);
 13         if(data == 1)
 14         {
 15             forkReturn = fork();
 16             if(forkReturn > 0)
 17             {
 18 
 19             }
 20             else if(forkReturn == 0)
 21             {
 22                 while(1)
 23                 {
 24                     printf("Do net request.pid = %d\n",getpid());
 25                     sleep(3);
 26                 }
 27             }
 28             else
 29             {
 30                 printf("error\n");
 31             }
 32         }
 33         else
 34         {
 35             printf("do nothing\n");
 36         }
 37     }
 38     return 0;
 39 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hxIJSXaM-1610010410789)(en-resource://database/672:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oMJqRHPq-1610010410791)(en-resource://database/674:1)]
可以看到,除了test的父程序外輸入了2次1,就模擬了有2次請求到達,建立了2個子程序去處理請求

目的二

一個程序要執行一個不同的程式,子程序從fork返回後立即呼叫exec。

案例需求:當父程序檢測到輸入1的時候,建立子程序把配置檔案的欄位值修改掉。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6     int number = 0;
  7     int forkReturn = 0;
  8     while(1)
  9     {
 10         printf("Please input a number:");
 11         scanf("%d",&number);
 12         if(number == 1)
 13         {
 14             forkReturn = fork();
 15             if(forkReturn == 0)
 16             {
 17                 execl("../fileCompile/changeConfig","changeConfig","../fileCompile/file1",NULL);
 18             }
 19         }
 20     }
 21     return 0;
 22 }

注意:要操作不同目錄下的執行檔案操作不同目錄下的檔案時file1也要用絕對路徑表示;
子程序執行execl()函式成功後會執行changeConfig.c程式碼,該程式碼最後有return 0,會結束子程序,所以不用exit和wait來結束子程序。

fork總結

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-X1EzyvEg-1610010410793)(en-resource://database/676:1)]

建立程序vfork()函式

vfork()和fork()函式的區別

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-C8MBr9rf-1610010410794)(en-resource://database/678:1)]

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         while(1)
 14         {
 15             printf("Father fork,pid = %d\n",getpid());
 16             printf("data = %d\n",data);
 17             sleep(3);
 18         }
 19     }
 20     else if(forkReturn == 0)
 21     {
 22         while(1)
 23         {
 24             printf("Child fork,pid = %d\n",getpid());
 25             data++;
 26             if(data == 3)
 27             {
 28                 exit(0);
 29             }
 30             sleep(1);
 31         }
 32     }
 33     return 0;
 34 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QFLGQZhj-1610010410795)(en-resource://database/680:1)]

程序退出

5種正常退出,3種異常退出
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-vaXoqCBj-1610010410796)(en-resource://database/682:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-mkOhiZq5-1610010410798)(en-resource://database/684:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YFCiHibc-1610010410799)(en-resource://database/686:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-riHlP3dS-1610010410801)(en-resource://database/688:1)]

父程序等待子程序退出

為什麼父程序要等待子程序退出

因為要檢查子程序的任務執行的情況

子程序退出狀態不被收集會變成殭屍程序

父程序沒有呼叫wait()函式收集子程序的退出狀態

1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         while(1)
 14         {
 15             printf("Father fork,pid = %d\n",getpid());
 16             printf("data = %d\n",data);
 17             sleep(3);
 18         }
 19     }
 20     else if(forkReturn == 0)
 21     {
 22         while(1)
 23         {
 24             printf("Child fork,pid = %d\n",getpid());
 25             data++;
 26             if(data == 3)
 27             {
 28                 exit(0);
 29             }
 30             sleep(1);
 31         }
 32     }
 33     return 0;
 34 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-92BR3Mwg-1610010410802)(en-resource://database/690:1)]
Z代表Zombie,即殭屍程序

父程序等待子程序退出並收集退出狀態

在父程序中呼叫wait()函式

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     forkReturn = vfork();
 11     if(forkReturn > 0)
 12     {
 13         wait(NULL);
 14         while(1)
 15         {
 16             printf("Father fork,pid = %d\n",getpid());
 17             printf("data = %d\n",data);
 18             sleep(3);
 19         }
 20     }
 21     else if(forkReturn == 0)
 22     {
 23         while(1)
 24         {
 25             printf("Child fork,pid = %d\n",getpid());
 26             data++;
 27             if(data == 3)
 28             {
 29                 exit(0);
 30             }
 31             sleep(1);
 32         }
 33     }
 34     return 0;
 35 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-DGrrqkSY-1610010410803)(en-resource://database/692:1)]

用wait()函式收集子程序退出的狀態

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YggN9MTv-1610010410804)(en-resource://database/694:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-DLuj6xMn-1610010410806)(en-resource://database/696:1)]

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = vfork();
 12     if(forkReturn > 0)
 13     {
 14         wait(&status);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NPIfpF8D-1610010410807)(en-resource://database/698:1)]
exit()退出中的狀態碼和wait()等待中的狀態碼相等,子程序退出狀態被收集

wait()函式

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0psSWgNQ-1610010410808)(en-resource://database/700:1)]

  • 父程序呼叫wait()函式時,如果有子程序在執行,要等子程序全部執行完,傳送子程序的退出狀態,等父程序中的wait()函式收集到退出狀態再執行父程序的程式碼段

  • 如果main函式裡沒有開啟任何子程序,此時呼叫wait()函式會直接報錯

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = fork();
 12     if(forkReturn > 0)
 13     {
 14         wait(&status);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-4ClmDOCK-1610010410809)(en-resource://database/702:1)]
呼叫的是fork()函式,本該父子程序交替執行,但在父程序中呼叫了wait()函式,所以要等子程序執行結束,父程序收集退出狀態後再執行父程序的程式碼。

waitpid()和wait()函式的區別

waitpid()函式有一個選項可以不阻塞呼叫者,但是不會收集子程序的狀態,子程序還是會變成殭屍程序
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cFUSNk7a-1610010410810)(en-resource://database/704:1)]
waitpid(forkReturn,&status,WNOHANG);這裡第一個引數是forkReturn,其值是fork建立的子程序ID,意思是等待其程序ID與子程序ID相等

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     int data = 0;
 10     int status = 0;
 11     forkReturn = fork();
 12     if(forkReturn > 0)
 13     {
 14         waitpid(forkReturn,&status,WNOHANG);
 15         printf("child quit,child status:%d\n",WEXITSTATUS(status));
 16         while(1)
 17         {
 18             printf("Father fork,pid = %d\n",getpid());
 19             printf("data = %d\n",data);
 20             sleep(3);
 21         }
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         while(1)
 26         {
 27             printf("Child fork,pid = %d\n",getpid());
 28             data++;
 29             if(data == 3)
 30             {
 31                 exit(8);
 32             }
 33             sleep(1);
 34         }
 35     }
 36     return 0;
 37 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QPgqmqOP-1610010410811)(en-resource://database/708:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-LpNqQGxm-1610010410813)(en-resource://database/706:1)]

孤兒程序

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QmtSmJ3Q-1610010410814)(en-resource://database/710:1)]
如下例:在父程序中只執行了一行程式碼就結束,而子程序在無限執行,當父程序結束而子程序未結束時,init程序會收留孤兒程序,成為其父程序,即ID=1的程序

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t forkReturn;
  9     forkReturn = fork();
 10     if(forkReturn > 0)
 11     {
 12         printf("Father fork,pid = %d\n",getpid());
 13     }
 14     else if(forkReturn == 0)
 15     {
 16         while(1)
 17         {
 18             printf("Child fork,pid = %d,father pid = %d\n",getpid(),getppid());
 19             sleep(1);
 20         }
 21     }
 22     return 0;
 23 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-b1XlJFsW-1610010410815)(en-resource://database/712:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-SAPScwAV-1610010410817)(en-resource://database/714:1)]

exec族函式

參考博文

exec族函式的作用

去執行另外一個程式(可執行檔案)。常在fork函式建立進城後使用,在fork函式建立的新程序中執行另一個程式

exec族函式描述

#include <unistd.h>
extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

exec函式族分別是:execl, execlp, execv, execvp,(最後兩個帶e的不常用)execle, execvpe

返回值:
exec函式族的函式執行成功後不會返回,呼叫失敗時,會設定errno並返回-1,然後從原程式的呼叫點接著往下執行。

引數說明:
path:可執行檔案的路徑名字
arg:可執行程式所帶的引數,第一個引數為可執行檔名字,沒有帶路徑且arg必須以NULL結束
file:如果引數file中包含/,則就將其視為路徑名,否則就按 PATH環境變數,在它所指定的各目錄中搜尋可執行檔案。

exec族函式中的字母含義:
l : 使用引數列表
p:使用檔名,並從PATH環境進行尋找可執行檔案,不加p使用絕對路徑
v:應先構造一個指向各引數的指標陣列,然後將該陣列的地址作為這些函式的引數
e:多了envp[]陣列,使用新的環境變數代替呼叫程序的環境變數

exec族函式的用法

帶l的execl()函式的用法

#include <unistd.h>
int execl(const char *path, const char *arg, ...);

案例:呼叫當前資料夾下的echoarg可執行檔案;echoarg用來列印初始引數;用perror()函式列印錯誤資訊

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main()
  5 {
  6     printf("before execl\n");
  7     if(execl("./bin/echoarg","echoarg","a",NULL) == -1)
  8     {
  9         printf("execl fail!\n");
 10         perror("why:");
 11     }
 12     printf("after execl\n");
 13     return 0;
 14 }

echoarg.c程式碼如下:

  1 #include <stdio.h>
  2 
  3 int main(int argc, char *argv[])
  4 {
  5     int i;
  6     for(i = 0; i < argc; i++)
  7     {
  8         printf("argv[%d] = %s\n",i,argv[i]);
  9     }
 10     return 0;
 11 }

因為執行檔案的路徑錯誤所有報錯
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zOfoc5yO-1610010410818)(en-resource://database/724:1)]
更改路徑後的正常執行:

1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main()
  5 {
  6     printf("before execl\n");
  7     if(execl("./echoarg","echoarg","a",NULL) == -1)
  8     {
  9         printf("execl fail!\n");
 10         perror("why:");
 11     }
 12     printf("after execl\n");
 13     return 0;
 14 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qut9a5uB-1610010410819)(en-resource://database/726:1)]
成功呼叫execl()函式後不執行後面的程式碼

帶p的execlp()函式

exaclp函式帶p,所以能通過環境變數PATH查詢到可執行檔案ps

//檔案execlp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函式原型:int execlp(const char *file, const char *arg, ...);
int main(void)
{
    printf("before execlp****\n");
    if(execlp("ps","ps","-l",NULL) == -1)
    {
        printf("execlp failed!\n");
    }
    printf("after execlp*****\n");
    return 0;
}

帶v不帶l的函式execv、execvp、execve

先構造一個指向各引數的指標陣列,然後將該陣列的地址作為這些函式的引數

//檔案execvp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函式原型:int execvp(const char *file, char *const argv[]);

int main(void)
{
    printf("before execlp****\n");
    char *argv[] = {"ps","-l",NULL};
    if(execvp("ps",argv) == -1) 
    {
        printf("execvp failed!\n");     
    }
    printf("after execlp*****\n");
    return 0;
}

system()函式

參考博文
通過檢視system()的原始碼發現:該函式是對execl()函式的封裝,其作用也是執行其他可執行檔案
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-LLFnKm3E-1610010410820)(en-resource://database/728:1)]
當system()函式執行成功還會接著執行後面的程式碼
需要的引數是需要敲的命令字串,如下:system("…/fileCompile/changeConfig …/fileCompile/file1");

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7     int number = 0;
  8     int forkReturn = 0;
  9     while(1)
 10     {
 11         printf("Please input a number:");
 12         scanf("%d",&number);
 13         if(number == 1)
 14         {
 15             forkReturn = fork();
 16             if(forkReturn == 0)
 17             {
 18                 //execl("../fileCompile/changeConfig","changeConfig","../fileCompile/file1",NULL);
 19                 system("../fileCompile/changeConfig ../fileCompile/file1");
 20             }
 21         }
 22     }
 23     return 0;
 24 }

popen()函式

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1VkWWNzF-1610010410822)(en-resource://database/730:1)]
參考博文
作用:
popen() 函式用於建立一個管道:其內部實現為呼叫 fork 產生一個子程序,執行一個 shell 以執行命令來開啟一個程序這個程序必須由 pclose() 函式關閉。功能和system()類似,但是能接收返回值

引數說明:
command: 是一個指向以 NULL 結束的 shell 命令字串的指標。這行命令將被傳到 bin/sh 並使用 -c 標誌,shell 將執行這個命令。
mode: 只能是讀或者寫中的一種,得到的返回值(標準 I/O 流)也具有和 type 相應的只讀或只寫型別。如果 type 是 “r” 則檔案指標連線到 command 的標準輸出;如果 type 是 “w” 則檔案指標連線到 command 的標準輸入。

返回值:
如果呼叫成功,則返回一個讀或者開啟檔案的指標,如果失敗,返回NULL

案例:用popen()函式執行ps指令並將ps顯示的內容寫到緩衝區內

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
  5     FILE *fp;
  6     char ptr[1024] = {0};
  7     fp = popen("ps","r");
  8     int nread = fread(ptr,1,1024,fp);
  9     printf("size:%d,read:%s\n",nread,ptr);
 10     return 0;
 11 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Vo9A1UTJ-1610010410823)(en-resource://database/732:1)]

單機程序間通訊

一臺機器上兩個不同的程序之間可以傳送和接收資訊,就是單機程序間通訊。
在兩臺機器上執行著兩個不同的程序,他們之間的通訊通過網路,屬於多機通訊。
程序間通訊IPC(InterProcess Communication)

單機程序間通訊的方式有:半雙工管道、全雙工管道、訊息佇列、訊號量、共享記憶體
多機程序間通訊的方式有:套接字、STREAMS

程序間的五種通訊方式

程序間通訊原理

程序間通訊的方式通常有管道(包括無名管道和命名管道)、訊息佇列、訊號量、共享儲存、Socket、Streams等。其中Socket和Streams支援不同主機上的兩個程序通訊。

管道(無名管道)pipe

特點:

  1. 半雙工,只能單向資料流動
  2. 用於親緣關係的程序之間的通訊(父子、兄弟程序之間)
  3. 可以看做是特殊的檔案,可以使用read和write函式,但它只存在於記憶體中

命名管道FIFO

FIFO是一種檔案型別。
特點:

  1. 可以在無關的程序之間通訊
  2. FIFO的路徑名與之相關,它以一種特殊裝置檔案形式存在於檔案系統中

當open一個FIFO時,沒有指定O_NONBLOCK(預設),只讀open要阻塞到某個其它程序為寫而開啟此FIFO。只寫open阻塞到有其它只讀程序開啟它。

訊息佇列

訊息佇列,是訊息的連結表,存放在核心中。一個訊息佇列由一個識別符號(即佇列ID)來標識。

特點:

  1. 訊息佇列是面向記錄的,其中的訊息具有特定的格式(一種結構體)以及特定的優先順序
  2. 訊息佇列獨立於傳送與接收程序。程序終止時,訊息佇列及其內容並不會被刪除
  3. 訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的次序讀取,也可以按訊息的型別讀取

共享記憶體

共享記憶體(Shared Memory),指兩個或多個程序共享一個給定的儲存區。

特點:

  1. 共享記憶體是最快的一種 IPC,因為程序是直接對記憶體進行存取。
  2. 因為多個程序可以同時操作,所以需要進行同步。
  3. 訊號量+共享記憶體通常結合在一起使用,訊號量用來同步對共享記憶體的訪問。

訊號

什麼是訊號?
程序1在執行程式碼,收到程序2發來的訊號,程序1進行處理。

訊號是在軟體層次上對中斷機制的一種模擬,是一種非同步通訊方式(軟中斷)

使用者程序對訊號的響應方式:

  1. 忽略訊號:對訊號不做任何處理,但是有兩個訊號不能忽略:即SIGKILL及SIGSTOP。
  2. 捕捉訊號(最常用的):定義訊號處理函式,當訊號發生時,執行相應的處理函式。
  3. 執行預設操作:Linux對每種訊號都規定了預設操作

怎麼用殺死訊號殺死程序?

  1. ps -aux|grep a.out檢視相應程序
  2. kill -l檢視訊號列表
  3. kill -9 (程序的id)殺死相應程序或kill -SIGKILL (程序的id)

訊號量

訊號量(semaphore)與已經介紹過的 IPC 結構不同,它是一個計數器。訊號量用於實現程序間的互斥與同步,而不是用於儲存程序間通訊資料。

訊號量用於程序間同步,若要在程序間傳遞資料需要結合共享記憶體

什麼是訊號量?
就像多個程序排隊到一個房間裡執行程式,訊號量就像進門的鑰匙,一個程序拿鎖進房間後其它程序等待,等他出房間放回鑰匙,下一個程序再拿這把鑰匙進門執行他的程式。取鑰匙相當於p操作,放回鑰匙相當於v操作

P操作(假設訊號量值為S):

  1. S-1
  2. 若S-1>=0,繼續執行程式
  3. 若S-1<0,後面的程式等待

V操作:

  1. S+1
  2. 若S+1>0,繼續執行程式
  3. 若S+1<=0,從該訊號的等待佇列中釋放一個等待程序,然後再返回原程序繼續執行或轉程序排程

訊號量值小於0,程式等待

無名管道程式設計

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hWTxVtje-1610010410824)(en-resource://database/756:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lDxnZhkp-1610010410825)(en-resource://database/758:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tLp9rSdX-1610010410826)(en-resource://database/760:1)]
pipe函式建立單向無名管道,需要2個fd陣列,返回0成功,-1失敗。注意:先建立管道再建立子程序

1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 
  6 int main()
  7 {
  8     printf("father pid = %d\n",getpid());
  9     pid_t forkReturn;
 10     int pipefd[2];
 11     if(pipe(pipefd) == -1)
 12     {
 13         printf("create pipe failed!\n");
 14         exit(-1);
 15     }
 16     forkReturn = fork();
 17     if(forkReturn > 0)
 18     {
 19         close(pipefd[0]);
 20         write(pipefd[1],"hello from father",strlen("hello from father"));
 21         printf("sent by father,father pid = %d\n",getpid());
 22     }
 23     else if(forkReturn == 0)
 24     {
 25         close(pipefd[1]);
 26         char *readBuf = (char *)malloc(128);
 27         read(pipefd[0],readBuf,128);
 28         printf("child read:%s\n",readBuf);
 29         printf("child pid = %d\n",getpid());
 30     }
 31     return 0;
 32 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wjQOrR4a-1610010410828)(en-resource://database/762:1)]

命名管道FIFO程式設計

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hBjSCZe2-1610010410829)(en-resource://database/764:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wpExwJIh-1610010410830)(en-resource://database/766:1)]
mkfifo()函式建立命名管道,需要引數管道檔案的命名路徑和許可權碼,返回0成功,-1失敗

1 #include <stdio.h>
  2 #include <errno.h>
  3 
  4 int main()
  5 {
  6     if(mkfifo("./file",00600) == -1 && errno == EEXIST)
  7     {
  8         printf("create fifo failed!\n");
  9         perror("reason");
 10     }
 11     return 0;
 12 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ty51l4AB-1610010410831)(en-resource://database/768:1)]
第一次建立了file檔案,第二次檔案已存在

read.c

 1 #include <stdio.h>
  2 #include <errno.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <fcntl.h>
  6 #include <unistd.h>
  7 
  8 int main()
  9 {
 10     if(mkfifo("./file",00600) == -1 && errno != EEXIST)
 11     {
 12         printf("create fifo failed!\n");
 13         perror("why");
 14     }
 15 
 16     int fd = open("./file",O_RDONLY);
 17     //blocked
 18     char readBuf[128];
 19     int nread = read(fd,readBuf,30);
 20     printf("read %d bytes,content:%s\n",nread,readBuf);
 21     close(fd);
 22     return 0;
 23 }

write.c

1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 int main()
  9 {
 10     int fd = open("./file",O_WRONLY);
 11     char *str = "hello from fifo";
 12     write(fd,str,strlen(str));
 13     close(fd);
 14     return 0;
 15 }

執行read,程式在open檔案後被堵塞,直到運行了write中的寫的程式
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wdinrVlp-1610010410832)(en-resource://database/770:1)]
開啟另一個控制檯執行
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9LCKFmbe-1610010410834)(en-resource://database/772:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9ERdv4cT-1610010410835)(en-resource://database/774:1)]

連續讀寫
read.c

1 #include <stdio.h>
  2 #include <errno.h>
  3 #include <sys/types.h>
  4 #include <sys/stat.h>
  5 #include <fcntl.h>
  6 #include <unistd.h>
  7 
  8 int main()
  9 {
 10     if(mkfifo("./file",00600) == -1 && errno != EEXIST)
 11     {
 12         printf("create fifo failed!\n");
 13         perror("why");
 14     }
 15 
 16     int fd = open("./file",O_RDONLY);
 17     //blocked
 18     char readBuf[128];
 19     while(1)
 20     {
 21         int nread = read(fd,readBuf,15);
 22         if(nread == 0)
 23         {
 24             break;
 25         }
 26         printf("read %d bytes,content:%s\n",nread,readBuf);
 27         sleep(1);
 28     }
 29     close(fd);
 30     return 0;
 31 }

write.c

1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <fcntl.h>
  7 
  8 int main()
  9 {
 10     int count = 0;
 11     int fd = open("./file",O_WRONLY);
 12     char *str = "hello from fifo";
 13     while(1)
 14     {
 15         write(fd,str,strlen(str));
 16         count++;
 17         if(count == 5)
 18         {
 19             break;
 20         }
 21     }
 22     close(fd);
 23     return 0;
 24 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-yVU9gfaK-1610010410836)(en-resource://database/780:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oHgQbjsE-1610010410837)(en-resource://database/778:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-epkHvGL9-1610010410838)(en-resource://database/776:1)]

訊息佇列程式設計

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-v9ODuAmc-1610010410839)(en-resource://database/785:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MlHAJm0u-1610010410840)(en-resource://database/787:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-y2pLdTmf-1610010410841)(en-resource://database/789:1)]
在以下兩種情況下,msgget將建立一個新的訊息佇列:

  • 如果沒有與鍵值key相對應的訊息佇列,並且flag中包含了IPC_CREAT標誌位。
  • key引數為IPC_PRIVATE。

如果msgflg同時指定了IPC_CREAT | IPC_EXCL,類似於open函式,函式呼叫失敗了errno == EEXIST

函式呼叫正常返回訊息佇列的識別符號(非負整數),失敗返回-1

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-pquoq3vO-1610010410842)(en-resource://database/791:1)]

單向傳送與接收訊息

msgServer.c從訊息佇列上接收訊息
根據key獲取某一條訊息佇列
從該訊息佇列上獲取訊息

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 
  6 struct msgbuf
  7 {
  8     long mtype;       /* message type, must be > 0 */
  9     char mtext[128];    /* message data */
 10 };
 11 
 12 
 13 int main()
 14 {
 15     int msgId = msgget(0x1234,IPC_CREAT|00777);
 16     if(msgId == -1)
 17     {
 18         printf("get msg queue failed!\n");
 19     }
 20 
 21     struct msgbuf readBuf;
 22     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
 23     printf("read from msg queue:%s\n",readBuf.mtext);
 24     return 0;
 25 }

msgClient.c向訊息佇列傳送訊息
根據key獲取指定的一條訊息佇列
向該訊息佇列傳送訊息

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 #include <string.h>
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf sendBuf = {888,"this is msg from msg queue"};
 23     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 24 
 25     return 0;
 26 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-l7a0R1Cd-1610010410844)(en-resource://database/793:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-izN8EfKS-1610010410845)(en-resource://database/795:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-FBYT1NkF-1610010410847)(en-resource://database/797:1)]

互相傳送和接收訊息

msgServer.c

 1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 #include <string.h>
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf readBuf;
 23     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
 24     printf("read from Server:%s\n",readBuf.mtext);
 25     struct msgbuf sendBuf = {999,"msg from server"};
 26     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 27     printf("send from server over!\n");
 28     return 0;
 29 }

msgClient.c

1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/msg.h>
  5 #include <string.h>
  6 
  7 struct msgbuf
  8 {
  9     long mtype;       /* message type, must be > 0 */
 10     char mtext[128];    /* message data */
 11 };
 12 
 13 
 14 int main()
 15 {
 16     int msgId = msgget(0x1234,IPC_CREAT|00777);
 17     if(msgId == -1)
 18     {
 19         printf("get msg queue failed!\n");
 20     }
 21 
 22     struct msgbuf sendBuf = {888,"this is msg from msg queue"};
 23     msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
 24     printf("msg send over!\n");
 25 
 26     struct msgbuf readBuf;
 27     msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),999,0);
 28     printf("client receive:%s\n",readBuf.mtext);
 29     return 0;
 30 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0DAtgG96-1610010410848)(en-resource://database/799:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9XxVYQNX-1610010410849)(en-resource://database/801:1)]

生成鍵值

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-e5Urj0QO-1610010410851)(en-resource://database/803:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-4YehhYg9-1610010410852)(en-resource://database/805:1)]

key_t key;
key = ftok(".",1);
int msgId = msgget(key,IPC_CREAT|00777);

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rxhFFPO5-1610010410853)(en-resource://database/807:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-3Tpv1PSq-1610010410854)(en-resource://database/809:1)]
客戶端和服務端的key相同,說明對同一個訊息佇列進行了訊息的傳送和獲取

刪除訊息佇列

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Qq1HTRqA-1610010410855)(en-resource://database/811:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bz3qwsyb-1610010410857)(en-resource://database/813:1)]

msgctl(msgId,IPC_RMID,NULL);

共享記憶體程式設計

1 #include <sys/shm.h>
2 // 建立或獲取一個共享記憶體:成功返回共享記憶體ID,失敗返回-1;size要是1兆的倍數
3 int shmget(key_t key, size_t size, int flag);
4 // 連線共享記憶體到當前程序的地址空間:成功返回指向共享記憶體的指標,失敗返回-1;addr為NULL由系統自動分配地址,flag為0為可讀可寫
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 斷開與共享記憶體的連線:成功返回0,失敗返回-1
7 int shmdt(void *addr); 
8 // 控制共享記憶體的相關資訊:成功返回0,失敗返回-1;cmd為IPC_RMID銷燬共享記憶體
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

程式設計思路
// 建立共享記憶體/有的話直接開啟
// 將共享記憶體的地址對映給操作的程序
// 資料交換
// 釋放共享記憶體
// 刪除共享記憶體

shmw.c往共享記憶體中寫入資料

  1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <string.h>
  7 
  8 int main()
  9 {
 10     key_t key = ftok(".",1);
 11     int shmId = shmget(key,1024*4,IPC_CREAT|00600);
 12     if(shmId == -1)
 13     {
 14         printf("shmget failed!\n");
 15         exit(-1);
 16     }
 17 
 18     char *shmaddr = shmat(shmId,0,0);
 19     printf("shmat succeessfully!\n");
 20     strcpy(shmaddr,"hello from shm");
 21 
 22     sleep(5);
 23 
 24     shmdt(shmaddr);
 25     shmctl(shmId,IPC_RMID,0);
 26 
 27     printf("quit!\n");
 28     return 0;
 29 }

shmr.c從共享記憶體中讀取資料

  1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <stdlib.h>
  5 #include <sys/types.h>
  6 #include <string.h>
  7 
  8 int main()
  9 {
 10     key_t key = ftok(".",1);
 11     int shmId = shmget(key,1024*4,0);
 12     if(shmId == -1)
 13     {
 14         printf("shmget failed!\n");
 15         exit(-1);
 16     }
 17 
 18     char *shmaddr = shmat(shmId,0,0);
 19     printf("shmat succeessfully!\n");
 20     printf("data:%s\n",shmaddr);
 21 
 22     shmdt(shmaddr);
 23     printf("quit!\n");
 24     return 0;
 25 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-2smkdEai-1610010410858)(en-resource://database/815:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YGL6NFTJ-1610010410859)(en-resource://database/817:1)]

訊號程式設計

初級訊號程式設計

// signal函式將訊號signum設定成handler函式作為訊號的處理
sighandler_t signal(int signum, sighandler_t handler);

// 引數int為signum訊號編號
typedef void (*sighandler_t)(int);

// kill()函式用來對任何程序傳送訊號
int kill(pid_t pid, int sig);

捕捉訊號

  1 #include <stdio.h>
  2 #include <signal.h>
  3 
  4 void handler(int signum)
  5 {
  6     switch(signum)
  7     {
  8     case 2:
  9         printf("SIGINT\n");
 10         break;
 11     case 9:
 12         printf("SIGKILL\n");
 13         break;
 14     }
 15 }
 16 
 17 int main()
 18 {
 19     signal(SIGINT,handler);
 20     signal(SIGKILL,handler);
 21     while(1);
 22     return 0;
 23 }

按下Ctrl C,程式將Ctrl C的訊號SIGINT設定成了handler處理函式
SIGKILL訊號不能忽略
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-dzILk5ag-1610010410860)(en-resource://database/821:1)]

用程式設計實現kill指令

  1 #include <sys/types.h>
  2 #include <signal.h>
  3 #include <stdio.h>
  4 
  5 int main(int argc, char **argv)
  6 {
  7     int signum;
  8     int pid;
  9     signum = atoi(argv[1]);
 10     pid = atoi(argv[2]);
 11     int ret = kill(pid,signum);
 12     if(ret == 0)
 13     {
 14         printf("kill done!\n");
 15     }
 16     return 0;
 17 }

執行第一個程式signalDemo
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-oWGxTr8n-1610010410861)(en-resource://database/823:1)]
ps -aux|grep signalDemo檢視程序pid
執行程式殺死程序
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-jyKTL5P9-1610010410862)(en-resource://database/825:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9TDUC0fF-1610010410863)(en-resource://database/827:1)]

高階訊號程式設計

高階訊號程式設計區別於初級的是,高階訊號程式設計可以在捕獲訊號的同時傳送和接收資訊

// 用於接收並捕獲訊號。signum:要捕獲的訊號;act:捕獲訊號的動作(包括接收資訊);oldact:NULL為不儲存之前的操作
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
// act的結構體原型。指定sa_handler和初級訊號程式設計相同;必須要初始化sa_sigaction和sa_flags = SA_SIGINFO,int為捕獲的訊號,siginfo_t是結構體指標,void *content不為NULL,info就有資訊,info可接收哪些資訊見文件
struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };


// 用於傳送訊號。pid:目標程序號;sig:要傳送的訊號;value:其中含有要傳送的資訊
int sigqueue(pid_t pid, int sig, const union sigval value);
// value的聯合體,int:傳送整型,ptr:傳送其它型別
union sigval {
               int   sival_int;
               void *sival_ptr;
           };

seniorSignalRCV.c

  1 #include <signal.h>
  2 #include <stdio.h>
  3 #include <sys/types.h>
  4 #include <unistd.h>
  5 
  6 void handler(int signum, siginfo_t *info, void *context)
  7 {
  8     printf("get signum:%d\n",signum);
  9     if(context != NULL)
 10     {
 11         printf("get data:%d\n",info->si_int);
 12         printf("from process pid:%d\n",info->si_pid);
 13     }
 14 }
 15 int main()
 16 {
 17     printf("current process pid is:%d\n",getpid());
 18     struct sigaction act;
 19     act.sa_sigaction = handler;
 20     act.sa_flags = SA_SIGINFO;//be able to get message
 21     sigaction(SIGUSR1,&act,NULL);
 22     while(1);
 23     return 0;
 24 }

seniorSignalSD.c

  1 #include <signal.h>
  2 #include <stdio.h>
  3 
  4 int main(int argc, char **argv)
  5 {
  6     int signum;
  7     int pid;
  8     signum = atoi(argv[1]);
  9     pid = atoi(argv[2]);
 10     union sigval value;
 11     value.sival_int = 100;
 12     sigqueue(pid,signum,value);
 13     printf("done!\n");
 14     return 0;
 15 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-nXSNyjkn-1610010410864)(en-resource://database/829:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qw6KQrb2-1610010410865)(en-resource://database/831:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QeCSOPcz-1610010410866)(en-resource://database/833:1)]

訊號量程式設計

// 建立一個新訊號量或取得一個已有訊號量;num_sems為訊號量個數,flags為IPC_CREAT|00600建立訊號量
int semget(key_t key, int num_sems, int sem_flags);

// 改變訊號量的值;nsops為結構體sembuf的個數即訊號量的個數,如果有2個訊號量則sembuf是個結構體陣列有2個結構體
int semop(int semid, struct sembuf *sops, unsigned nsops);

// 直接控制訊號量資訊,初始化訊號量;sem_num為控制第幾個訊號量從第0個開始;command為SETVAL給訊號量初始化,初始化時第四個引數為semun的結構體
int semctl(int sem_id, int sem_num, int command, ...);

讓子程序先執行,父程序後執行
不加鎖前父程序程式先執行,父程序先去拿鎖(訊號量值減1),但沒有,(訊號量值小於0為-1)則等待。執行子程序,結束後把鎖放回去,父程序再去拿鎖執行。

程式設計思路:
// 建立訊號量/獲取已有訊號量
// 初始化訊號量
// 子程式執行,把鎖放回
// 父程序拿鎖執行把鎖放回
// 銷燬訊號量
 1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/ipc.h>
  4 #include <sys/sem.h>
  5 
  6 union semun {
  7     int val;    /* Value for SETVAL */
  8     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
  9     unsigned short  *array;  /* Array for GETALL, SETALL */
 10     struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
 11 };
 12 
 13 void pGetKey(int id)
 14 {
 15     struct sembuf sop;
 16     sop.sem_num = 0;
 17     sop.sem_op = -1;
 18     sop.sem_flg = SEM_UNDO;
 19     semop(id,&sop,1);
 20     printf("get key done!\n");
 21 }
 22 
 23 void vPutBackKey(int id)
 24 {
 25     struct sembuf sop;
 26     sop.sem_num = 0;
 27     sop.sem_op = 1;
 28     sop.sem_flg = SEM_UNDO;
 29     semop(id,&sop,1);
 30     printf("put back the key done!\n");
 31 }
 32 
 33 int main()
 34 {
 35     key_t key = ftok(".",1);
 36     int semid = semget(key,1,IPC_CREAT|00600);
 37     union semun initsem;
 38     initsem.val = 0;
 39     semctl(semid,0,SETVAL,initsem);
 40 
 41     int forkpid = fork();
 42     if(forkpid > 0)
 43     {
 44         pGetKey(semid);
 45         printf("this is father fork\n");
 46         vPutBackKey(semid);
 47         semctl(semid,0,IPC_RMID);
 48     }
 49     else if(forkpid == 0)
 50     {
 51         printf("this is child fork\n");
 52         vPutBackKey(semid);
 53     }
 54     else
 55     {
 56         printf("fork error!\n");
 57     }
 58     return 0;
 59 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-g2vaN6od-1610010410867)(en-resource://database/835:1)]

訊息佇列、共享記憶體、訊號量綜合

訊息佇列用來發送和接收指令,共享記憶體用來發送和接收資訊,訊號量用來保證訊息同步

服務端邏輯
// 建立共享記憶體
// 連線共享記憶體
// 建立訊息佇列
// 建立訊號量
// 初始化訊號量
// 不斷讀取訊息佇列中的指令
// 當讀到r,p操作,接收共享記憶體中的資訊,v操作
// 當讀到q,退出迴圈
// 清空並銷燬共享記憶體
// 銷燬訊息佇列
// 銷燬訊號量

server.c

 1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <sys/types.h>
  5 #include <sys/msg.h>
  6 #include <sys/sem.h>
  7 
  8 void pGetKey(int semid)
  9 {
 10     struct sembuf initsem;
 11     initsem.sem_num = 0;
 12     initsem.sem_op = -1;
 13     initsem.sem_flg = SEM_UNDO;
 14     semop(semid,&initsem,1);
 15     printf("get key done!\n");
 16 }
 17 
 18 void vPutBackKey(int semid)
 19 {
 20     struct sembuf initsem;
 21     initsem.sem_num = 0;
 22     initsem.sem_op = 1;
 23     initsem.sem_flg = SEM_UNDO;
 24     semop(semid,&initsem,1);
 25     printf("put back key done!\n");
 26 }
 27 struct msgbuf {
 28     long mtype;       /* message type, must be > 0 */
 29     char mtext[1];
 30 };
 31 union semun {
 32     int val;    /* Value for SETVAL */
 33     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
 34     unsigned short  *array;  /* Array for GETALL, SETALL */
 35     struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
 36 };
 37 
 38 int main()
 39 {
 40     key_t key = ftok(".",1);
 41     struct msgbuf buf;
 42     int shmid = shmget(key,1024*4,IPC_CREAT|00600);
 43     char *shmaddr = shmat(shmid,NULL,0);
44 
 45     int msgid = msgget(key,IPC_CREAT|00600);
 46 
 47     int semid = semget(key,1,IPC_CREAT|00600);
 48     union semun initsem;
 49     initsem.val = 1;
 50     semctl(semid,0,SETVAL,initsem);
 51     while(1)
 52     {
 53         msgrcv(msgid,&buf,1,888,0);
 54         if(buf.mtext[0] == 'q')
 55             break;
 56         if(buf.mtext[0] == 'r')
 57         {
 58             pGetKey(semid);
 59             printf("read from shm:%s\n",shmaddr);
 60             vPutBackKey(semid);
 61         }
 62     }
 63     shmdt(shmaddr);
 64 
 65     shmctl(shmid,IPC_RMID,0);
 66     msgctl(msgid,IPC_RMID,0);
 67     semctl(semid,0,IPC_RMID);
 68     return 0;
 69 }
客戶端邏輯
// 獲取共享記憶體
// 連線共享記憶體
// 獲取訊息佇列
// 獲取訊號量
// 不斷讓客戶端請求使用者輸入指令
// 當用戶輸入r,p操作,向共享記憶體傳送資訊,v操作,清空標準輸入緩衝區,傳送r指令給訊息佇列
// 當用戶輸入q,傳送q指令給訊息佇列,退出迴圈
// 清空共享記憶體

client.c

 1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <sys/types.h>
  5 #include <sys/msg.h>
  6 #include <sys/sem.h>
  7 
  8 struct msgbuf
  9 {
 10     long mtype;
 11     char mtext[1];
 12 };
 13 
 14 void pGetKey(int semid)
 15 {
 16     struct sembuf initsem;
 17     initsem.sem_num = 0;
 18     initsem.sem_op = -1;
 19     initsem.sem_flg = SEM_UNDO;
 20     semop(semid,&initsem,1);
 21     printf("get key\n");
 22 }
 23 
 24 void vPutBackKey(int semid)
 25 {
 26     struct sembuf initsem;
 27     initsem.sem_num = 0;
 28     initsem.sem_op = 1;
 29     initsem.sem_flg = SEM_UNDO;
 30     semop(semid,&initsem,1);
 31     printf("put back key\n");
 32 }
 33 
 34 int main()
 35 {
 36     key_t key = ftok(".",1);
 37     int shmid = shmget(key,1024*4,0);
 38     char *shmaddr = shmat(shmid,NULL,0);
 39     int msgid = msgget(key,0);
 40     int semid = semget(key,0,0);
 41     printf("***************************************\n");
 42     printf("*                 IPC                 *\n");
 43     printf("*    Input r to send data to server.  *\n");
 44     printf("*    Input q to quit.                 *\n");
 45     printf("***************************************\n");
 46     char c;
 47     struct msgbuf msg;
 48     int flag = 1;
 49     while(flag)
 50     {
 51         printf("Please input command:");
 52         scanf("%c",&c);
 53         switch(c)
 54         {
 55         case 'r':
 56             printf("Data to send:");
 57             pGetKey(semid);
 58             scanf("%s",shmaddr);
 59             vPutBackKey(semid);
 60             while((c = getchar()) != '\n' && c != EOF);
 61             msg.mtype = 888;
 62             msg.mtext[0] = 'r';
 63             msgsnd(msgid,&msg,sizeof(msg.mtext),0);
 64             break;
 65         case 'q':
 66             msg.mtype = 888;
 67             msg.mtext[0] = 'q';
 68             msgsnd(msgid,&msg,sizeof(msg.mtext),0);
 69             flag = 0;
 70             break;
 71         default:
 72             printf("wrong input!\n");
 73             while((c = getchar()) != '\n' && c != EOF);
 74             break;
 75         }
 76     }
 77     shmdt(shmaddr);
 78     return 0;
 79 }

等待接收訊息佇列
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-G3Q1PTZy-1610010410868)(en-resource://database/839:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-3KbqqXZ2-1610010410869)(en-resource://database/841:1)]
輸入r,客戶端執行p操作,S=0,繼續執行向共享記憶體傳送資訊,客戶端執行v操作,S=1,向訊息佇列傳送指令
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-OWmiMex7-1610010410870)(en-resource://database/843:1)]
服務端收到r指令,執行p操作,S=0,繼續執行接收共享記憶體資訊,執行v操作,S=1
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZsiMRSr7-1610010410871)(en-resource://database/845:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Ms4e1m9l-1610010410872)(en-resource://database/847:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-vHROPhl9-1610010410873)(en-resource://database/849:1)]

同步、互斥、訊號量初值的設定、併發和並行

同步: 程序之間相互依賴。前一個程序的輸出作為後一個程序的輸入,當第一個程序沒有輸出時第二個程序必須等待。(同步一般訊號量初值設為0,例子見訊號量程式設計)

互斥: 兩個或兩個以上的程序,不能同時進入關於同一組共享變數的臨界區域,一個程序正在訪問臨界資源,另一個要訪問該資源的程序必須等待。(互斥的訊號量初值一般設為1,例子見訊息佇列、共享記憶體、訊號量綜合)

關於訊號量的初值的設定
訊號量的初值為最多允許幾個程序同時進入互斥段,訊號量的值為負值,意思是有幾個程序在等待

互斥段: 被加鎖和解鎖包圍的程式碼段

臨界資源: 一次僅允許一個程序使用的資源

併發: 幾個程序都處於執行狀態,兩種併發方式是同步和互斥

並行: 在多處理器上程序可以重疊執行(在同一時間執行多個程序)

五種通訊方式總結

  1. 管道:速度慢,容量有限,只有父子程序能通訊
  2. FIFO:任何程序間都能通訊,但速度慢
  3. 訊息佇列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完資料的問題
  4. 訊號量:不能傳遞複雜訊息,只能用來同步
  5. 共享記憶體區:能夠很容易控制容量,速度快,但要保持同步,比如一個程序在寫的時候,另一個程序要注意讀寫的問題,相當於執行緒中的執行緒安全,當然,共享記憶體區同樣可以用作執行緒間通訊,不過沒這個必要,執行緒間本來就已經共享了同一程序內的一塊記憶體

執行緒

參考文件

執行緒概述

程序與執行緒的區別

典型的UNIX/Linux程序可以看成只有一個控制執行緒:一個程序在同一時刻只做一件事情。有了多個控制執行緒後,在程式設計時可以把程序設計成在同一時刻做不止一件事,每個執行緒各自處理獨立的任務。

程序是執行緒的容器。一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。

程序的所有資訊對該程序的所有執行緒都是共享的,包括可執行的程式文字、程式的全域性記憶體和堆記憶體、棧以及檔案描述符

程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響。執行緒沒有單獨的地址空間,一個執行緒死掉就等於整個程序死掉。
所以多程序的程式要比多執行緒的程式健壯,但在程序切換時,耗費資源較大,效率要差一些。

使用執行緒的理由

  • 【理由一】:
    啟動一個執行緒所花費的空間遠遠小於啟動一個程序所花費的空間,而且,執行緒間彼此切換所需的時間也遠遠小於程序間切換所需要的時間。
    啟動一個新的程序必須分配給它獨立的地址空間,建立眾多的資料表來維護它的程式碼段、堆疊段和資料段,而運行於一個程序中的多個執行緒,它們彼此之間使用相同的地址空間,共享大部分資料。

  • 【理由二】:
    執行緒間通訊更方便。
    對不同程序來說,它們具有獨立的資料空間,要進行資料的傳遞只能通過通訊的方式進行,這種方式不僅費時,而且很不方便。執行緒則不然,由於同一程序下的執行緒之間共享資料空間,所以一個執行緒的資料可以直接為其它執行緒所用,這不僅快捷,而且方便。

非同步

執行緒的同步執行是順序執行;非同步執行是同時執行

執行緒同步和互斥

執行緒同步: 按預定的先後次序執行
執行緒互斥: 對於共享的程序系統資源,只允許一個執行緒訪問,讓其它執行緒等待。

執行緒程式設計

建立執行緒pthread_create和pthread_self

建立執行緒threadCreate.c

// 建立執行緒
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
第1個引數:要建立的執行緒名,指標型別
第2個引數:屬性,定為NULL
第3個引數:建立執行緒後要執行的函式,函式指標型別
第4個引數:第3個引數函式中的引數

// 獲取當前執行緒號
pthread_self()
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 void *func1(void *arg)
  5 {
  6     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  7     printf("t1:param is %d\n",*((int *)arg));
  8 }
  9 
 10 int main()
 11 {
 12     int ret;
 13     pthread_t t1;
 14     int param = 100;
 15     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 16     if(ret == 0)
 17     {
 18         printf("create thread t1 successfully!\n");
 19     }
 20     printf("main thread:%ld\n",(unsigned long)pthread_self());
 21     while(1);// 為了讓主執行緒阻塞,待t1執行緒執行
 22     return 0;
 23 }

while(1);// 為了讓主執行緒阻塞不讓它結束執行,待t1執行緒執行。如果主執行緒退出會釋放程序空間,讓其它執行緒沒有空間執行程式
編譯指令:gcc threadCreate.c -lpthread -o threadCreate
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-yhAWrXFv-1610010410874)(en-resource://database/851:1)]

阻塞主執行緒

阻塞主執行緒threadBlock.c

// 執行緒等待
int pthread_join(pthread_t thread, void **retval);
第1個引數:要等待這個執行緒執行完畢
第2個引數:退出等待時的狀態碼,NULL表示不關心狀態碼呼叫pthread_join函式將等待指定的執行緒終止,但並不獲得執行緒的終止狀態。
子執行緒不呼叫exit,join也阻塞呼叫它的執行緒直到子執行緒結束

// 執行緒退出
void pthread_exit(void *retval);
引數:執行緒退出時的狀態碼
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 void *func1(void *arg)
  5 {
  6     static int ret = 10;
  7     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  8     printf("t1:param is %d\n",*((int *)arg));
  9     pthread_exit((void *)&ret);
 10 }
 11 
 12 int main()
 13 {
 14     int ret;
 15     pthread_t t1;
 16     int param = 100;
 17     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 18     if(ret == 0)
 19     {
 20         printf("create thread t1 successfully!\n");
 21     }
 22     printf("main thread:%ld\n",(unsigned long)pthread_self());
 23     int *pret = NULL;
 24     pthread_join(t1,(void *)&pret);
 25     printf("main thread:t1 quit %d\n",*pret);
 26     return 0;
 27 }

static int ret = 10;指定為static是為了func1執行結束後仍然保持ret的值傳給pthread_join;
int *pret = NULL; pthread_join(t1,(void *)&pret);由於引數要求是二級指標,所以一級指標的值可以改變,初值設為NULL也沒事,會被pthread_exit中的狀態碼重新賦值;
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QTx4onkj-1610010410875)(en-resource://database/853:1)]

退出狀態碼為字串

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 void *func1(void *arg)
  5 {
  6     static int ret = 10;
  7     static char *p = "t1 is run out!";
  8     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
  9     printf("t1:param is %d\n",*((int *)arg));
 10     pthread_exit((void *)p);
 11 }
 12 
 13 int main()
 14 {
 15     int ret;
 16     pthread_t t1;
 17     int param = 100;
 18     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 19     if(ret == 0)
 20     {
 21         printf("create thread t1 successfully!\n");
 22     }
 23     printf("main thread:%ld\n",(unsigned long)pthread_self());
 24     char *pret = NULL;
 25     pthread_join(t1,(void *)&pret);
 26     printf("main thread:%s\n",pret);
 27     return 0;
 28 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IuxlyrGd-1610010410876)(en-resource://database/855:1)]

執行緒共享記憶體空間的程式碼驗證

建立全域性變數g_data,在各執行緒中++,如果列印結果都不重複說明各執行緒共享這個資料

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 int g_data = 50;
  5 
  6 void *func1(void *arg)
  7 {
  8     static char *p = "t1 is run out!";
  9     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
 10     printf("t1:param is %d\n",*((int *)arg));
 11     while(1)
 12     {
 13         printf("t1:g_data = %d\n",g_data++);
 14         sleep(1);
 15     }
 16     pthread_exit((void *)p);
 17 }
 18 
 19 void *func2(void *arg)
 20 {
 21     static char *p = "t2 is run out!";
 22     printf("t2:%ld is created!\n",(unsigned long)pthread_self());
 23     printf("t2:param is %d\n",*((int *)arg));
 24     while(1)
 25     {
 26         printf("t2:g_data = %d\n",g_data++);
 27         sleep(1);
 28     }
 29     pthread_exit((void *)p);
 30 }
 31 
 32 void *func3(void *arg)
 33 {
 34     static char *p = "t3 is run out!";
 35     printf("t3:%ld is created!\n",(unsigned long)pthread_self());
 36     printf("t3:param is %d\n",*((int *)arg));
 37     while(1)
 38     {
 39         printf("t3:g_data = %d\n",g_data++);
 40         sleep(1);
 41     }
 42     pthread_exit((void *)p);
 43 }
 44 
 45 int main()
 46 {
 47     int ret;
 48     pthread_t t1;
 49     pthread_t t2;
 50     pthread_t t3;
 51     int param = 100;
 52     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 53     if(ret == 0)
 54     {
 55         printf("main:create thread t1 successfully!\n");
 56     }
 57 
 58     ret = pthread_create(&t2,NULL,func2,(void *)&param);
 59     if(ret == 0)
 60     {
 61         printf("main:create thread t2 successfully!\n");
 62     }
 63 
 64     ret = pthread_create(&t3,NULL,func3,(void *)&param);
 65     if(ret == 0)
 66     {
 67         printf("main:create thread t3 successfully!\n");
 68     }
 69 
 70     printf("main thread:%ld\n",(unsigned long)pthread_self());
 71     while(1)
 72     {
 73         printf("main thread:g_data = %d\n",g_data++);
 74         sleep(1);
 75     }
 76     char *pret1 = NULL;
 77     char *pret2 = NULL;
 78     char *pret3 = NULL;
 79     pthread_join(t1,(void *)&pret1);
 80     pthread_join(t2,(void *)&pret2);
 81     pthread_join(t3,(void *)&pret3);
 82     printf("main thread:%s\n",pret1);
 83     printf("main thread:%s\n",pret2);
 84     printf("main thread:%s\n",pret3);
 85     return 0;
 86 }

從列印結果看,沒有重複的資料,說明各執行緒是共享全域性變數的資料的
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-SY3jKKr1-1610010410878)(en-resource://database/857:1)]

執行緒同步之互斥鎖加鎖解鎖

// 建立鎖
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
第1個引數:指標型別
第2個引數:鎖的屬性,預設設定為NULL

// 銷燬鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 加鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 解鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);

mutex是一把鎖,被加鎖和解鎖包圍的程式碼是共享資源,在某個執行緒執行時程式會保證加過鎖的程式碼連續執行,哪怕裡面有sleep,解鎖後各執行緒再競爭,再執行各執行緒中加鎖的程式碼
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 pthread_mutex_t mutex;
  5 
  6 void *func1(void *arg)
  7 {
  8     pthread_mutex_lock(&mutex);
  9     printf("t1:%ld is created!\n",(unsigned long)pthread_self());
 10     printf("t1:param is %d\n",*((int *)arg));
 11     pthread_mutex_unlock(&mutex);
 12 }
 13 
 14 void *func2(void *arg)
 15 {
 16     pthread_mutex_lock(&mutex);
 17     printf("t2:%ld is created!\n",(unsigned long)pthread_self());
 18     printf("t2:param is %d\n",*((int *)arg));
 19     pthread_mutex_unlock(&mutex);
 20 }
 21 
 22 void *func3(void *arg)
 23 {
 24     pthread_mutex_lock(&mutex);
 25     printf("t3:%ld is created!\n",(unsigned long)pthread_self());
 26     printf("t3:param is %d\n",*((int *)arg));
 27     pthread_mutex_unlock(&mutex);
 28 }
 29 
 30 int main()
 31 {
 32     int ret;
 33     pthread_t t1;
 34     pthread_t t2;
 35     pthread_t t3;
 36     int param = 100;
 37 
 38     pthread_mutex_init(&mutex,NULL);
 39 
 40     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 41     if(ret == 0)
 42     {
 43         printf("main:create thread t1 successfully!\n");
 44     }
 45 
 46     ret = pthread_create(&t2,NULL,func2,(void *)&param);
 47     if(ret == 0)
 48     {
 49         printf("main:create thread t2 successfully!\n");
 50     }
 51 
 52     ret = pthread_create(&t3,NULL,func3,(void *)&param);
 53     if(ret == 0)
 54     {
 55         printf("main:create thread t3 successfully!\n");
 56     }
 57 
 58     printf("main thread:%ld\n",(unsigned long)pthread_self());
 59     pthread_join(t1,NULL);
 60     pthread_join(t2,NULL);
 61     pthread_join(t3,NULL);
 62 
 63     pthread_mutex_destroy(&mutex);
 64     return 0;
 65 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-QP03Jx9g-1610010410879)(en-resource://database/859:1)]

對共享記憶體的加鎖和解鎖

對g_data這個共享資料加鎖解鎖,讓其它執行緒睡1秒,當其它執行緒同時在sleep時只能執行t1執行緒,再用鎖保證其程式碼的執行
一定要在t2執行緒也加鎖解鎖,因為t1中g_data沒有等於3時,由於t2也加了鎖,t2會被阻塞直到t1一直加到3解鎖以後再能執行t2

並不是被加鎖和解鎖包圍的程式碼先執行

對互斥量進行加鎖後,任何其它試圖再次對互斥量加鎖的執行緒將會被阻塞直到當前執行緒釋放該互斥鎖。

如果釋放互斥鎖時有多個執行緒阻塞,所有在該互斥鎖上的阻塞執行緒都會變成可執行狀態,第一個變為可執行狀態的執行緒可以對互斥量加鎖,其他執行緒將會看到互斥鎖依然被鎖住,只能回去等待它重新變為可用。在這種方式下,每次只有一個執行緒可以向前執行
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 pthread_mutex_t mutex;
  5 
  6 int g_data = 0;
  7 
  8 void *func1(void *arg)
  9 {
 10     pthread_mutex_lock(&mutex);
 11     while(1)
 12     {
 13         printf("t1:g_data = %d\n",g_data++);
 14         sleep(1);
 15         if(g_data == 3)
 16         {
 17             pthread_mutex_unlock(&mutex);
 18             printf("t1:quit==================================================================\n");
 19             pthread_exit(NULL);
 20         }
 21     }
 22 }
 23 
 24 void *func2(void *arg)
 25 {
 26     while(1)
 27     {
 28         printf("t2:g_data = %d\n",g_data);
 29         pthread_mutex_lock(&mutex);
 30         g_data++;
 31         pthread_mutex_unlock(&mutex);
 32         sleep(1);
 33     }
 34 }
 35 
 36 int main()
 37 {
 38     int ret;
 39     pthread_t t1;
 40     pthread_t t2;
 41     int param = 100;
 42 
 43     pthread_mutex_init(&mutex,NULL);
 44     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 45     ret = pthread_create(&t2,NULL,func2,(void *)&param);
 46 
 47     while(1)
 48     {
 49         printf("main:g_data = %d\n",g_data);
 50         sleep(1);
 51     }
 52     pthread_join(t1,NULL);
 53     pthread_join(t2,NULL);
 54 
 55     pthread_mutex_destroy(&mutex);
 56     return 0;
 57 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xysNcGmA-1610010410880)(en-resource://database/861:1)]

死鎖

死鎖要有2把鎖,在兩個執行緒中分別相鄰加鎖,順序顛倒,這樣讓2把鎖分別在2個不同的執行緒中,導致第二次想拿鎖的時候都拿不到,兩個執行緒都會被阻塞等待,造成死鎖
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 pthread_mutex_t mutex;
  5 pthread_mutex_t mutex2;
  6 
  7 int g_data = 0;
  8 
  9 void *func1(void *arg)
 10 {
 11     int i;
 12     pthread_mutex_lock(&mutex);
 13     sleep(1);
 14     pthread_mutex_lock(&mutex2);
 15     for(i = 0; i < 5; i++)
 16     {
 17         printf("t1:g_data = %d\n",g_data++);
 18     }
 19     pthread_mutex_unlock(&mutex);
 20 }
 21 
 22 void *func2(void *arg)
 23 {
 24     while(1)
 25     {
 26         pthread_mutex_lock(&mutex2);
 27         sleep(1);
 28         pthread_mutex_lock(&mutex);
 29         printf("t2:g_data = %d\n",g_data);
 30         g_data++;
 31         pthread_mutex_unlock(&mutex);
 32         sleep(1);
 33     }
 34 }
 35 
 36 int main()
 37 {
 38     int ret;
 39     pthread_t t1;
 40     pthread_t t2;
 41     int param = 100;
 42 
 43     pthread_mutex_init(&mutex,NULL);
 44     pthread_mutex_init(&mutex2,NULL);
 45     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 46     ret = pthread_create(&t2,NULL,func2,(void *)&param);
 47 
 48     printf("main:g_data = %d\n",g_data);
 49 
 50     pthread_join(t1,NULL);
 51     pthread_join(t2,NULL);
 52 
 53     pthread_mutex_destroy(&mutex);
 54     pthread_mutex_destroy(&mutex2);
 55     return 0;
 56 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-SW4cw4PI-1610010410881)(en-resource://database/865:1)]
下圖是程式執行的順序,3和4都被阻塞等待造成死鎖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JBeTAqA3-1610010410882)(en-resource://database/863:1)]

執行緒的條件控制實現執行緒的同步

讓其它執行緒對g_data++,當g_data==3時在t1執行緒列印quit

// 初始化
第2個引數:屬性,一般為NULL
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

// 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

// 觸發
int pthread_cond_signal(pthread_cond_t *cond);

// 銷燬
int pthread_cond_destroy(pthread_cond_t *cond);
  1 #include <stdio.h>
  2 #include <pthread.h>
  3 
  4 pthread_mutex_t mutex;
  5 pthread_cond_t cond;
  6 
  7 int g_data = 0;
  8 
  9 void *func1(void *arg)
 10 {
 11     while(1)
 12     {
 13         pthread_cond_wait(&cond,&mutex);
 14         printf("t1:quit==================================================================\n");
 15         printf("t1:g_data = %d\n",g_data);
 16         g_data = 0;
 17         sleep(1);
 18     }
 19 }
 20 
 21 void *func2(void *arg)
 22 {
 23     while(1)
 24     {
 25         printf("t2:g_data = %d\n",g_data);
 26         pthread_mutex_lock(&mutex);
 27         g_data++;
 28         if(g_data == 3)
 29         {
 30             pthread_cond_signal(&cond);
 31         }
 32         pthread_mutex_unlock(&mutex);
 33         sleep(1);
 34     }
 35 }
 36 
 37 int main()
 38 {
 39     int ret;
 40     pthread_t t1;
 41     pthread_t t2;
 42     int param = 100;
 43 
 44     pthread_cond_init(&cond,NULL);
 45 
 46     pthread_mutex_init(&mutex,NULL);
 47     ret = pthread_create(&t1,NULL,func1,(void *)&param);
 48     ret = pthread_create(&t2,NULL,func2,(void *)&param);
 49 
 50     pthread_join(t1,NULL);
 51     pthread_join(t2,NULL);
 52 
 53     pthread_mutex_destroy(&mutex);
 54     pthread_cond_destroy(&cond);
 55     return 0;
 56 }

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-H3Iw3BKT-1610010410883)(en-resource://database/869:1)]

用巨集作靜態初始化,使用init函式的都是動態初始化
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0QAxUCxG-1610010410884)(en-resource://database/871:1)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-d5VCg3WM-1610010410885)(en-resource://database/873:1)]

執行緒同步——生產者消費者問題

生產者消費者問題

有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能併發執行,在兩者之間設定一個具有多個緩衝區的緩衝池,生產者將它生產的產品放入一個緩衝區中,消費者可以從緩衝區中取走產品進行消費,顯然生產者和消費者之間必須保持同步,即不允許消費者到一個空的緩衝區中取產品,也不允許生產者向一個已經放入產品的緩衝區中再次投放產品。

用互斥鎖程式設計實現

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 #define LOOP_COUNT 5
  4 
  5 pthread_mutex_t mutex;
  6 pthread_t thread_producer,thread_consumer;
  7 
  8 void *producer(void *arg)
  9 {
 10     //let main thread run first,and then thread_produncer run
 11     sleep(1);
 12     int count = 0;
 13     while(count++ < LOOP_COUNT)
 14     {
 15         pthread_mutex_lock(&mutex);
 16         printf("producer put a product to buffer.\n");
 17         pthread_mutex_unlock(&mutex);
 18         //insure thread_consumer to run
 19         sleep(3);
 20     }
 21 //  pthread_exit(NULL);
 22 }
 23 
 24 void *consumer(void *arg)
 25 {
 26     //let main thread run first,and run after thread_producer
 27     sleep(2);
 28     int count = 0;
 29     while(count++ < LOOP_COUNT)
 30     {
 31         pthread_mutex_lock(&mutex);
 32         printf("consumer get a product from buffer.\n");
 33         pthread_mutex_unlock(&mutex);
 34         //insure thread_producer to run
 35         sleep(3);
 36     }
 37 //  pthread_exit(NULL);
 38 }
 39 
 40 int main()
 41 {
 42     pthread_mutex_init(&mutex,NULL);
 43     int ret_producer = pthread_create(&thread_producer,NULL,producer,NULL);
 44     if(ret_producer == 0)
 45     {
 46         printf("main thread:create thread producer successfully!\n");
 47     }
 48     int ret_consumer = pthread_create(&thread_consumer,NULL,consumer,NULL);
 49     if(ret_consumer == 0)
 50     {
 51         printf("main thread:create thread consumer successfully!\n");
 52     }
 53 
 54     pthread_join(thread_producer,NULL);
 55     pthread_join(thread_consumer,NULL);
 56 
 57     pthread_mutex_destroy(&mutex);
 58     return 0;
 59 }

讓子執行緒睡2秒以上才能保證不搶執行緒
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rN4HVHOy-1610010410886)(en-resource://database/875:1)]

網路程式設計

網路程式設計概述

單機程序間通訊的方式都是依賴Linux核心,無法實現多機通訊

網路程式設計一定涉及地址去找到裝置,地址包括IP地址(找到裝置)和埠號(各種服務跑在不同的埠上,埠號為了區分這些不同的服務)

資料的交流:協議(資料格式)eg. 微控制器和PC通訊用uart串列埠協議,Linux網路程式設計用tcp\udp協議(Socket套接字網路程式設計)

tcp協議:面向連線,要先連線A和B端,然後類似打電話進行交流,可靠,用於精確的資料傳輸場景

udp協議:面向報文。不用連線,A給B發信息,B不一定能收到,不可靠,用於大量的資料傳輸場景
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-a3emr3nf-1610010410887)(en-resource://database/877:1)]

位元組序

位元組序就是指多位元組資料在計算機記憶體中儲存或網路傳輸時各位元組的儲存順序。

8(bit)位1個位元組(Byte),16位1個字(Word),雙字是32位,1個位元組1個儲存空間,雙字是4個儲存空間

常見的位元組序是:小端位元組序(LE)和大端位元組序(BE)
小端位元組序:將低序位元組儲存在起始地址,即將04存在4000,03存在4001地址…
大端位元組序:將高序位元組儲存在起始地址,反過來
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-T6ZghFfb-1610010410888)(en-resource://database/879:1)]

x86系列CPU都是little-endian的位元組序,網路位元組順序採用big endian排序方式

socket程式設計步驟

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tHtGalCk-1610010410889)(en-resource://database/881:1)]

socket網路程式設計實戰

尋找關鍵字在哪個庫裡

cd /usr/include/切換到include目錄
grep "struct sockaddr_in {" * -nir

  • 在當前目錄查詢
    r 遞迴查詢
    n 找出來顯示行號
    i 不區分大小寫來找

網路程式設計實戰

使用者埠號一般使用5000~9000,低於3000的埠號是供作業系統使用的
5000~9000儲存的是主機位元組序,要轉換成網路位元組序
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZsRtvApF-1610010410890)(en-resource://database/883:1)]

// 建立套接字
int socket(int domain, int type, int protocol);
第1個引數:使用什麼協議,AF_INET是IPv4網路協議
第2個引數:協議的型別,SOCK_STREAM是tcp協議,提供按序列的可靠的雙向基於連線的位元組流
SOCK_DGRAM是udp協議,採用報文的方式
第3個引數:如果只使用一個協議,這個引數就是0,否則是其它協議的程式碼
返回值:失敗返回-1,成功返回socket的檔案描述符

// 為套接字新增資訊
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第1個引數:socket的檔案描述符
第2個引數:改用struct sockaddr_in s_addr;但要強轉成struct sockaddr *,切換到usr/include目錄,查詢該欄位grep "struct sockaddr_in {" * -nir,在vi linux/in.h +184中184行
sin_family:地址家族為AF_INET
sin_port:埠號範圍5000~9000,要將主機埠號轉換成網路位元組序,呼叫htons()函式
sin_addr:是個結構體,通過grep "struct in_addr {" * -nir搜尋,在vi linux/in.h +56
sin_addr.s_addr:結構體中的一個成員,是網路地址,要將主機地址轉換成網路能識別的地址,用inet_aton("127.0.0.1");
第3個引數:第2個引數結構體的大小

//監聽網路連線
int listen(int sockfd, int backlog);
第1個引數:socket的檔案描述符
第2個引數:最大監聽數量
如果沒有監聽到會卡在這

//監聽到有客戶端接入,接受一個完成3次握手的連線
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
第1個引數:socket的檔案描述符
第2、3個引數:NULL
返回值:成功返回一個客戶端的socket檔案描述符,失敗返回-1

socket服務端

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 //#include <linux/in.h>
  5 #include <arpa/inet.h>
  6 #include <netinet/in.h>
  7 #include <stdlib.h>
  8 
  9 int main()
 10 {
 11     //1.socket
 12     int socket_fd = socket(AF_INET,SOCK_STREAM,0);
 13     if(socket_fd == -1)
 14     {
 15         perror("socket");
 16         exit(-1);
 17     }
 18     //2.bind
 19     struct sockaddr_in s_addr;
 20     s_addr.sin_family = AF_INET;
 21     s_addr.sin_port = htons(8989);
 22     inet_aton("192.168.0.100",&s_addr.sin_addr);
 23     bind(socket_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
 24     //3.listen
 25     listen(socket_fd,10);
 26     //4.accept
 27     int c_fd = accept(socket_fd,NULL,NULL);
 28     //5.read
 29     //6.write
 30     printf("connect!\n");
 31     while(1);
 32     return 0;
 33 }

用Windows測試連線

首先檢視Linux虛擬機器的ip地址
ifconfig命令檢視到是192.168.0.100

在Windows系統中執行cmd,ping 192.168.0.100能通說明電腦作為客戶端和Linux虛擬機器服務端能連線

在Linux系統中執行服務端程式
在Windows系統中用telnet連線
telnet 192.168.0.100 8989

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-6jLtVVxL-1610010410892)(en-resource://database/889:1)]
連線成功

連線成功時獲取客戶端地址

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <sys/socket.h>
  4 //#include <linux/in.h>
  5 #include <arpa/inet.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 
  9 int main()
 10 {
 11     int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
 12     if(socket_fd == -1)
 13     {
 14         perror("socket");
 15         exit(-1);
 16     }
 17 
 18     struct sockaddr_in s_addr;
 19     struct sockaddr_in c_addr;
 20     memset(&s_addr, 0, sizeof(struct sockaddr_in));
 21     memset(&c_addr, 0, sizeof(struct sockaddr_in));
 22 
 23     s_addr.sin_family = AF_INET;
 24     s_addr.sin_port = htons(8989);
 25     inet_aton("192.168.0.100", &s_addr.sin_addr);
 26     bind(socket_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
 27     listen(socket_fd, 10);
 28     int clen = sizeof(struct sockaddr_in);
 29     int client_fd = accept(socket_fd, (struct sockaddr *)&c_addr, &clen);
 30     if(client_fd == -1)
 31     {
 32         perror("client");
 33         exit(-1);
 34     }
 35     printf("get connected:%s\n", inet_ntoa(c_addr.sin_addr));
 36     while(1);
 37     return 0;
 38 }

在Windows系統中的cmd命令列中輸入telnet 192.168.0.100 8989
連線成功
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qv7MZahk-1610010410893)(en-resource://database/890:1)]