Linux進程開發(十七)

Linux進程開發(十七)

進程概述

程序和進程

 

 

 

 單道、多道程序設計

 

 

時間片

 

 並行和併發

 

 

 

 

 

進程控制塊(PCB)

 

 

 

 

 

 

進程狀態轉換

進程的狀態

 

 

 

 進程相關命令

 

 

 

 

 

 

 

 

 

./a.out & :程序在後臺運行,輸出也可以打印在前臺上面。

 

進程號和相關函數

 

 

進程創建

進程創建

 

 

fork()讀時共享,寫時子進程才copy複製一份程序和虛擬地址空間,使得當父進程寫的時候改變了物理地址,但是子進程指向的還是原來的物理空間指向的值,兩者的物理空間指向的值可能不同。

 

 

父子進程關係和GDB多進程調試

 

 

 

 

(面試常考)GDB多進程調試

 

exec函數族

exec函數族介紹

 

 

 exec函數族作用圖解

 

 

 

 

 

 

 

 

 

 

 

 

 

代碼:

 1 /*  
 2     #include <unistd.h>
 3     int execl(const char *path, const char *arg, ...);
 4         - 參數:
 5             - path:需要指定的執行的文件的路徑或者名稱
 6                 a.out /home/nowcoder/a.out 推薦使用絕對路徑
 7                 ./a.out hello world
 8 
 9             - arg:是執行可執行文件所需要的參數列表
10                 第一個參數一般沒有什麼作用,爲了方便,一般寫的是執行的程序的名稱
11                 從第二個參數開始往後,就是程序執行所需要的的參數列表。
12                 參數最後需要以NULL結束(哨兵)
13 
14         - 返回值:
15             只有當調用失敗,纔會有返回值,返回-1,並且設置errno
16             如果調用成功,沒有返回值。
17 
18 */
19 #include <unistd.h>
20 #include <stdio.h>
21 
22 int main() {
23 
24 
25     // 創建一個子進程,在子進程中執行exec函數族中的函數
26     pid_t pid = fork();
27 
28     if(pid > 0) {
29         // 父進程
30         printf("i am parent process, pid : %d\n",getpid());
31         sleep(1);
32     }else if(pid == 0) {
33         // 子進程
34         // execl("hello","hello",NULL);
35 
36         execl("/bin/ps", "ps", "aux", NULL);
37         perror("execl");
38         printf("i am child process, pid : %d\n", getpid());
39 
40     }
41 
42     for(int i = 0; i < 3; i++) {
43         printf("i = %d, pid = %d\n", i, getpid());
44     }
45 
46 
47     return 0;
48 }
 1 /*  
 2     #include <unistd.h>
 3     int execlp(const char *file, const char *arg, ... );
 4         - 會到環境變量中查找指定的可執行文件,如果找到了就執行,找不到就執行不成功。
 5         - 參數:
 6             - file:需要執行的可執行文件的文件名
 7                 a.out
 8                 ps
 9 
10             - arg:是執行可執行文件所需要的參數列表
11                 第一個參數一般沒有什麼作用,爲了方便,一般寫的是執行的程序的名稱
12                 從第二個參數開始往後,就是程序執行所需要的的參數列表。
13                 參數最後需要以NULL結束(哨兵)
14 
15         - 返回值:
16             只有當調用失敗,纔會有返回值,返回-1,並且設置errno
17             如果調用成功,沒有返回值。
18 
19 
20         int execv(const char *path, char *const argv[]);
21         argv是需要的參數的一個字符串數組
22         char * argv[] = {"ps", "aux", NULL};
23         execv("/bin/ps", argv);
24 
25         int execve(const char *filename, char *const argv[], char *const envp[]);
26         char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};
27 
28 
29 */
30 #include <unistd.h>
31 #include <stdio.h>
32 
33 int main() {
34 
35 
36     // 創建一個子進程,在子進程中執行exec函數族中的函數
37     pid_t pid = fork();
38 
39     if(pid > 0) {
40         // 父進程
41         printf("i am parent process, pid : %d\n",getpid());
42         sleep(1);
43     }else if(pid == 0) {
44         // 子進程
45         execlp("ps", "ps", "aux", NULL);
46 
47         printf("i am child process, pid : %d\n", getpid());
48 
49     }
50 
51     for(int i = 0; i < 3; i++) {
52         printf("i = %d, pid = %d\n", i, getpid());
53     }
54 
55 
56     return 0;
57 }
1 #include <stdio.h>
2 
3 int main() {    
4 
5     printf("hello, world\n");
6 
7     return 0;
8 }

 

進程退出、孤兒進程、殭屍進程

進程退出

 

 

 孤兒進程

 

 

 殭屍進程

 

 

代碼

exit函數

 1 /*
 2     #include <stdlib.h>
 3     void exit(int status);
 4 
 5     #include <unistd.h>
 6     void _exit(int status);
 7 
 8     status參數:是進程退出時的一個狀態信息。父進程回收子進程資源的時候可以獲取到。
 9 */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 
14 int main() {
15 
16     printf("hello\n");
17     printf("world");
18 
19     // exit(0);
20     _exit(0);
21     
22     return 0;
23 }

 

孤兒進程

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 
 5 int main() {
 6 
 7     // 創建子進程
 8     pid_t pid = fork();
 9 
10     // 判斷是父進程還是子進程
11     if(pid > 0) {
12 
13         printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
14 
15     } else if(pid == 0) {
16         sleep(1);
17         // 當前是子進程
18         printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
19        
20     }
21 
22     // for循環
23     for(int i = 0; i < 3; i++) {
24         printf("i : %d , pid : %d\n", i , getpid());
25     }
26 
27     return 0;
28 }

 

殭屍進程

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 
 5 int main() {
 6 
 7     // 創建子進程
 8     pid_t pid = fork();
 9 
10     // 判斷是父進程還是子進程
11     if(pid > 0) {
12         while(1) {
13             printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
14             sleep(1);
15         }
16 
17     } else if(pid == 0) {
18         // 當前是子進程
19         printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
20        
21     }
22 
23     // for循環
24     for(int i = 0; i < 3; i++) {
25         printf("i : %d , pid : %d\n", i , getpid());
26     }
27 
28     return 0;
29 }

 

wait函數

進程回收

 

 退出信息相關宏函數

 

 

 

 

waitpid函數

 

 

代碼

 1 /*
 2     #include <sys/types.h>
 3     #include <sys/wait.h>
 4     pid_t waitpid(pid_t pid, int *wstatus, int options);
 5         功能:回收指定進程號的子進程,可以設置是否阻塞。
 6         參數:
 7             - pid:
 8                 pid > 0 : 某個子進程的pid
 9                 pid = 0 : 回收當前進程組的所有子進程    
10                 pid = -1 : 回收所有的子進程,相當於 wait()  (最常用)
11                 pid < -1 : 某個進程組的組id的絕對值,回收指定進程組中的子進程
12             - options:設置阻塞或者非阻塞
13                 0 : 阻塞
14                 WNOHANG : 非阻塞
15             - 返回值:
16                 > 0 : 返回子進程的id
17                 = 0 : options=WNOHANG, 表示還有子進程活着
18                 = -1 :錯誤,或者沒有子進程了
19 */
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 
26 int main() {
27 
28     // 有一個父進程,創建5個子進程(兄弟)
29     pid_t pid;
30 
31     // 創建5個子進程
32     for(int i = 0; i < 5; i++) {
33         pid = fork();
34         if(pid == 0) {
35             break;
36         }
37     }
38 
39     if(pid > 0) {
40         // 父進程
41         while(1) {
42             printf("parent, pid = %d\n", getpid());
43             sleep(1);
44 
45             int st;
46             // int ret = waitpid(-1, &st, 0);
47             int ret = waitpid(-1, &st, WNOHANG);
48 
49             if(ret == -1) {
50                 break;
51             } else if(ret == 0) {
52                 // 說明還有子進程存在
53                 continue;
54             } else if(ret > 0) {
55 
56                 if(WIFEXITED(st)) {
57                     // 是不是正常退出
58                     printf("退出的狀態碼:%d\n", WEXITSTATUS(st));
59                 }
60                 if(WIFSIGNALED(st)) {
61                     // 是不是異常終止
62                     printf("被哪個信號幹掉了:%d\n", WTERMSIG(st));
63                 }
64 
65                 printf("child die, pid = %d\n", ret);
66             }
67            
68         }
69 
70     } else if (pid == 0){
71         // 子進程
72          while(1) {
73             printf("child, pid = %d\n",getpid());    
74             sleep(1);       
75          }
76         exit(0);
77     }
78 
79     return 0; 
80 }

 

進程間通信

進程間通信概念

 

 

Linux系統進程間通信方式(面試必須背住)

 

 

匿名管道

 

 管道的特點

 

 

 

 

爲什麼可以使用管道進行進程間通信?

 

 

管道的數據結構

邏輯上是環形的隊列,實際數據結構不是環形的。

 

 

匿名管道的使用

 

 

 

 

 

 代碼

pipe.c

 1 /*
 2     #include <unistd.h>
 3     int pipe(int pipefd[2]);
 4         功能:創建一個匿名管道,用來進程間通信。
 5         參數:int pipefd[2] 這個數組是一個傳出參數。
 6             pipefd[0] 對應的是管道的讀端
 7             pipefd[1] 對應的是管道的寫端
 8         返回值:
 9             成功 0
10             失敗 -1
11 
12     管道默認是阻塞的:如果管道中沒有數據,read阻塞,如果管道滿了,write阻塞
13 
14     注意:匿名管道只能用於具有關係的進程之間的通信(父子進程,兄弟進程)
15 */
16 
17 // 子進程發送數據給父進程,父進程讀取到數據輸出
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 int main() {
25 
26     // 在fork之前創建管道
27     int pipefd[2];
28     int ret = pipe(pipefd);
29     if(ret == -1) {
30         perror("pipe");
31         exit(0);
32     }
33 
34     // 創建子進程
35     pid_t pid = fork();
36     if(pid > 0) {
37         // 父進程
38         printf("i am parent process, pid : %d\n", getpid());
39 
40         // 關閉寫端
41         close(pipefd[1]);
42         
43         // 從管道的讀取端讀取數據
44         char buf[1024] = {0};
45         while(1) {
46             int len = read(pipefd[0], buf, sizeof(buf));
47             printf("parent recv : %s, pid : %d\n", buf, getpid());
48             
49             // 向管道中寫入數據
50             //char * str = "hello,i am parent";
51             //write(pipefd[1], str, strlen(str));
52             //sleep(1);
53         }
54 
55     } else if(pid == 0){
56         // 子進程
57         printf("i am child process, pid : %d\n", getpid());
58         // 關閉讀端
59         close(pipefd[0]);
60         char buf[1024] = {0};
61         while(1) {
62             // 向管道中寫入數據
63             char * str = "hello,i am child";
64             write(pipefd[1], str, strlen(str));
65             //sleep(1);
66 
67             // int len = read(pipefd[0], buf, sizeof(buf));
68             // printf("child recv : %s, pid : %d\n", buf, getpid());
69             // bzero(buf, 1024);
70         }
71         
72     }
73     return 0;
74 }

 

 fpathconf.c

 1 #include <unistd.h>
 2 #include <sys/types.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 
 7 int main() {
 8 
 9     int pipefd[2];
10 
11     int ret = pipe(pipefd);
12 
13     // 獲取管道的大小
14     long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
15 
16     printf("pipe size : %ld\n", size);
17 
18     return 0;
19 }

 

匿名管道的使用

 

 


 

 

 匿名管道通信案例

代碼:

 1 /*
 2     實現 ps aux | grep xxx 父子進程間通信
 3     
 4     子進程: ps aux, 子進程結束後,將數據發送給父進程
 5     父進程:獲取到數據,過濾
 6     pipe()
 7     execlp()
 8     子進程將標準輸出 stdout_fileno 重定向到管道的寫端。  dup2
 9 */
10 
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <wait.h>
17 
18 int main() {
19 
20     // 創建一個管道
21     int fd[2];
22     int ret = pipe(fd);
23 
24     if(ret == -1) {
25         perror("pipe");
26         exit(0);
27     }
28 
29     // 創建子進程
30     pid_t pid = fork();
31 
32     if(pid > 0) {
33         // 父進程
34         // 關閉寫端
35         close(fd[1]);
36         // 從管道中讀取
37         char buf[1024] = {0};
38 
39         int len = -1;
40         while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
41             // 過濾數據輸出
42             printf("%s", buf);
43             memset(buf, 0, 1024);
44         }
45 
46         wait(NULL);
47 
48     } else if(pid == 0) {
49         // 子進程
50         // 關閉讀端
51         close(fd[0]);
52 
53         // 文件描述符的重定向 stdout_fileno -> fd[1]
54         dup2(fd[1], STDOUT_FILENO);
55         // 執行 ps aux
56         execlp("ps", "ps", "aux", NULL);
57         perror("execlp");
58         exit(0);
59     } else {
60         perror("fork");
61         exit(0);
62     }
63 
64 
65     return 0;
66 }

 

管道的讀寫特點和管道設置爲非阻塞的

管道的讀寫特點

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

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

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

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

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

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

 

代碼

 1 #include <unistd.h>
 2 #include <sys/types.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 #include <fcntl.h>
 7 /*
 8     設置管道非阻塞
 9     int flags = fcntl(fd[0], F_GETFL);  // 獲取原來的flag
10     flags |= O_NONBLOCK;            // 修改flag的值
11     fcntl(fd[0], F_SETFL, flags);   // 設置新的flag
12 */
13 int main() {
14 
15     // 在fork之前創建管道
16     int pipefd[2];
17     int ret = pipe(pipefd);
18     if(ret == -1) {
19         perror("pipe");
20         exit(0);
21     }
22 
23     // 創建子進程
24     pid_t pid = fork();
25     if(pid > 0) {
26         // 父進程
27         printf("i am parent process, pid : %d\n", getpid());
28 
29         // 關閉寫端
30         close(pipefd[1]);
31         
32         // 從管道的讀取端讀取數據
33         char buf[1024] = {0};
34 
35         int flags = fcntl(pipefd[0], F_GETFL);  // 獲取原來的flag
36         flags |= O_NONBLOCK;            // 修改flag的值
37         fcntl(pipefd[0], F_SETFL, flags);   // 設置新的flag
38 
39         while(1) {
40             int len = read(pipefd[0], buf, sizeof(buf));
41             printf("len : %d\n", len);
42             printf("parent recv : %s, pid : %d\n", buf, getpid());
43             memset(buf, 0, 1024);
44             sleep(1);
45         }
46 
47     } else if(pid == 0){
48         // 子進程
49         printf("i am child process, pid : %d\n", getpid());
50         // 關閉讀端
51         close(pipefd[0]);
52         char buf[1024] = {0};
53         while(1) {
54             // 向管道中寫入數據
55             char * str = "hello,i am child";
56             write(pipefd[1], str, strlen(str));
57             sleep(5);
58         }
59         
60     }
61     return 0;
62 }

 

有名管道介紹和使用

有名管道

 

 

 

 有名管道的使用

 

 

代碼

mkfifo.c

 1 /*
 2     創建fifo文件
 3     1.通過命令: mkfifo 名字
 4     2.通過函數:int mkfifo(const char *pathname, mode_t mode);
 5 
 6     #include <sys/types.h>
 7     #include <sys/stat.h>
 8     int mkfifo(const char *pathname, mode_t mode);
 9         參數:
10             - pathname: 管道名稱的路徑
11             - mode: 文件的權限 和 open 的 mode 是一樣的
12                     是一個八進制的數
13         返回值:成功返回0,失敗返回-1,並設置錯誤號
14 
15 */
16 
17 #include <stdio.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 
23 int main() {
24 
25 
26     // 判斷文件是否存在
27     int ret = access("fifo1", F_OK);
28     if(ret == -1) {
29         printf("管道不存在,創建管道\n");
30         
31         ret = mkfifo("fifo1", 0664);
32 
33         if(ret == -1) {
34             perror("mkfifo");
35             exit(0);
36         }       
37 
38     }
39 
40     
41 
42     return 0;
43 }

 

read.c

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <sys/stat.h>
 4 #include <stdlib.h>
 5 #include <unistd.h>
 6 #include <fcntl.h>
 7 
 8 // 從管道中讀取數據
 9 int main() {
10 
11     // 1.打開管道文件
12     int fd = open("test", O_RDONLY);
13     if(fd == -1) {
14         perror("open");
15         exit(0);
16     }
17 
18     // 讀數據
19     while(1) {
20         char buf[1024] = {0};
21         int len = read(fd, buf, sizeof(buf));
22         if(len == 0) {
23             printf("寫端斷開連接了...\n");
24             break;
25         }
26         printf("recv buf : %s\n", buf);
27     }
28 
29     close(fd);
30 
31     return 0;
32 }

write.c

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <sys/stat.h>
 4 #include <stdlib.h>
 5 #include <unistd.h>
 6 #include <fcntl.h>
 7 #include <string.h>
 8 
 9 // 向管道中寫數據
10 /*
11     有名管道的注意事項:
12         1.一個爲只讀而打開一個管道的進程會阻塞,直到另外一個進程爲只寫打開管道
13         2.一個爲只寫而打開一個管道的進程會阻塞,直到另外一個進程爲只讀打開管道
14 
15     讀管道:
16         管道中有數據,read返回實際讀到的字節數
17         管道中無數據:
18             管道寫端被全部關閉,read返回0,(相當於讀到文件末尾)
19             寫端沒有全部被關閉,read阻塞等待
20     
21     寫管道:
22         管道讀端被全部關閉,進行異常終止(收到一個SIGPIPE信號)
23         管道讀端沒有全部關閉:
24             管道已經滿了,write會阻塞
25             管道沒有滿,write將數據寫入,並返回實際寫入的字節數。
26 */
27 int main() {
28 
29     // 1.判斷文件是否存在
30     int ret = access("test", F_OK);
31     if(ret == -1) {
32         printf("管道不存在,創建管道\n");
33         
34         // 2.創建管道文件
35         ret = mkfifo("test", 0664);
36 
37         if(ret == -1) {
38             perror("mkfifo");
39             exit(0);
40         }       
41 
42     }
43 
44     // 3.以只寫的方式打開管道
45     int fd = open("test", O_WRONLY);
46     if(fd == -1) {
47         perror("open");
48         exit(0);
49     }
50 
51     // 寫數據
52     for(int i = 0; i < 100; i++) {
53         char buf[1024];
54         sprintf(buf, "hello, %d\n", i);
55         printf("write data : %s\n", buf);
56         write(fd, buf, strlen(buf));
57         sleep(1);
58     }
59 
60     close(fd);
61 
62     return 0;
63 }

 

有名管道實現簡單版聊天功能

有名管道的使用

 代碼

chatA.c

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <stdlib.h>
 6 #include <fcntl.h>
 7 #include <string.h>
 8 
 9 int main() {
10 
11     // 1.判斷有名管道文件是否存在
12     int ret = access("fifo1", F_OK);
13     if(ret == -1) {
14         // 文件不存在
15         printf("管道不存在,創建對應的有名管道\n");
16         ret = mkfifo("fifo1", 0664);
17         if(ret == -1) {
18             perror("mkfifo");
19             exit(0);
20         }
21     }
22 
23     ret = access("fifo2", F_OK);
24     if(ret == -1) {
25         // 文件不存在
26         printf("管道不存在,創建對應的有名管道\n");
27         ret = mkfifo("fifo2", 0664);
28         if(ret == -1) {
29             perror("mkfifo");
30             exit(0);
31         }
32     }
33 
34     // 2.以只寫的方式打開管道fifo1
35     int fdw = open("fifo1", O_WRONLY);
36     if(fdw == -1) {
37         perror("open");
38         exit(0);
39     }
40     printf("打開管道fifo1成功,等待寫入...\n");
41     // 3.以只讀的方式打開管道fifo2
42     int fdr = open("fifo2", O_RDONLY);
43     if(fdr == -1) {
44         perror("open");
45         exit(0);
46     }
47     printf("打開管道fifo2成功,等待讀取...\n");
48 
49     char buf[128];
50 
51     // 4.循環的寫讀數據
52     while(1) {
53         memset(buf, 0, 128);
54         // 獲取標準輸入的數據
55         fgets(buf, 128, stdin);
56         // 寫數據
57         ret = write(fdw, buf, strlen(buf));
58         if(ret == -1) {
59             perror("write");
60             exit(0);
61         }
62 
63         // 5.讀管道數據
64         memset(buf, 0, 128);
65         ret = read(fdr, buf, 128);
66         if(ret <= 0) {
67             perror("read");
68             break;
69         }
70         printf("buf: %s\n", buf);
71     }
72 
73     // 6.關閉文件描述符
74     close(fdr);
75     close(fdw);
76 
77     return 0;
78 }

chatB.c

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <stdlib.h>
 6 #include <fcntl.h>
 7 #include <string.h>
 8 
 9 int main() {
10 
11     // 1.判斷有名管道文件是否存在
12     int ret = access("fifo1", F_OK);
13     if(ret == -1) {
14         // 文件不存在
15         printf("管道不存在,創建對應的有名管道\n");
16         ret = mkfifo("fifo1", 0664);
17         if(ret == -1) {
18             perror("mkfifo");
19             exit(0);
20         }
21     }
22 
23     ret = access("fifo2", F_OK);
24     if(ret == -1) {
25         // 文件不存在
26         printf("管道不存在,創建對應的有名管道\n");
27         ret = mkfifo("fifo2", 0664);
28         if(ret == -1) {
29             perror("mkfifo");
30             exit(0);
31         }
32     }
33 
34     // 2.以只讀的方式打開管道fifo1
35     int fdr = open("fifo1", O_RDONLY);
36     if(fdr == -1) {
37         perror("open");
38         exit(0);
39     }
40     printf("打開管道fifo1成功,等待讀取...\n");
41     // 3.以只寫的方式打開管道fifo2
42     int fdw = open("fifo2", O_WRONLY);
43     if(fdw == -1) {
44         perror("open");
45         exit(0);
46     }
47     printf("打開管道fifo2成功,等待寫入...\n");
48 
49     char buf[128];
50 
51     // 4.循環的讀寫數據
52     while(1) {
53         // 5.讀管道數據
54         memset(buf, 0, 128);
55         ret = read(fdr, buf, 128);
56         if(ret <= 0) {
57             perror("read");
58             break;
59         }
60         printf("buf: %s\n", buf);
61 
62         memset(buf, 0, 128);
63         // 獲取標準輸入的數據
64         fgets(buf, 128, stdin);
65         // 寫數據
66         ret = write(fdw, buf, strlen(buf));
67         if(ret == -1) {
68             perror("write");
69             exit(0);
70         }
71     }
72 
73     // 6.關閉文件描述符
74     close(fdr);
75     close(fdw);
76 
77     return 0;
78 }

 

內存映射

 

 

 

 

 

 

 

 

 

 

 

 

代碼

 1 /*
 2     #include <sys/mman.h>
 3     void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
 4         - 功能:將一個文件或者設備的數據映射到內存中
 5         - 參數:
 6             - void *addr: NULL, 由內核指定
 7             - length : 要映射的數據的長度,這個值不能爲0。建議使用文件的長度。
 8                     獲取文件的長度:stat lseek
 9             - prot : 對申請的內存映射區的操作權限
10                 -PROT_EXEC :可執行的權限
11                 -PROT_READ :讀權限
12                 -PROT_WRITE :寫權限
13                 -PROT_NONE :沒有權限
14                 要操作映射內存,必須要有讀的權限。
15                 PROT_READ、PROT_READ|PROT_WRITE
16             - flags :
17                 - MAP_SHARED : 映射區的數據會自動和磁盤文件進行同步,進程間通信,必須要設置這個選項
18                 - MAP_PRIVATE :不同步,內存映射區的數據改變了,對原來的文件不會修改,會重新創建一個新的文件。(copy on write)
19             - fd: 需要映射的那個文件的文件描述符
20                 - 通過open得到,open的是一個磁盤文件
21                 - 注意:文件的大小不能爲0,open指定的權限不能和prot參數有衝突。
22                     prot: PROT_READ                open:只讀/讀寫 
23                     prot: PROT_READ | PROT_WRITE   open:讀寫
24             - offset:偏移量,一般不用。必須指定的是4k的整數倍,0表示不便宜。
25         - 返回值:返回創建的內存的首地址
26             失敗返回MAP_FAILED,(void *) -1
27 
28     int munmap(void *addr, size_t length);
29         - 功能:釋放內存映射
30         - 參數:
31             - addr : 要釋放的內存的首地址
32             - length : 要釋放的內存的大小,要和mmap函數中的length參數的值一樣。
33 */
34 
35 /*
36     使用內存映射實現進程間通信:
37     1.有關係的進程(父子進程)
38         - 還沒有子進程的時候
39             - 通過唯一的父進程,先創建內存映射區
40         - 有了內存映射區以後,創建子進程
41         - 父子進程共享創建的內存映射區
42     
43     2.沒有關係的進程間通信
44         - 準備一個大小不是0的磁盤文件
45         - 進程1 通過磁盤文件創建內存映射區
46             - 得到一個操作這塊內存的指針
47         - 進程2 通過磁盤文件創建內存映射區
48             - 得到一個操作這塊內存的指針
49         - 使用內存映射區通信
50 
51     注意:內存映射區通信,是非阻塞。
52 */
53 
54 #include <stdio.h>
55 #include <sys/mman.h>
56 #include <fcntl.h>
57 #include <sys/types.h>
58 #include <unistd.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <wait.h>
62 
63 // 作業:使用內存映射實現沒有關係的進程間的通信。
64 int main() {
65 
66     // 1.打開一個文件
67     int fd = open("test.txt", O_RDWR);
68     int size = lseek(fd, 0, SEEK_END);  // 獲取文件的大小
69 
70     // 2.創建內存映射區
71     void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
72     if(ptr == MAP_FAILED) {
73         perror("mmap");
74         exit(0);
75     }
76 
77     // 3.創建子進程
78     pid_t pid = fork();
79     if(pid > 0) {
80         wait(NULL);
81         // 父進程
82         char buf[64];
83         strcpy(buf, (char *)ptr);
84         printf("read data : %s\n", buf);
85        
86     }else if(pid == 0){
87         // 子進程
88         strcpy((char *)ptr, "nihao a, son!!!");
89     }
90 
91     // 關閉內存映射區
92     munmap(ptr, size);
93 
94     return 0;
95 }

 

 

 

 

 思考問題

 

 

 內存映射的注意事項

1.如果對mmap的返回值(ptr)做++操作(ptr++), munmap是否能夠成功?

void * ptr = mmap(...);
ptr++; 可以對其進行++操作
munmap(ptr, len); // 錯誤,要保存地址

2.如果open時O_RDONLY, mmap時prot參數指定PROT_READ | PROT_WRITE會怎樣?

錯誤,返回MAP_FAILED
open()函數中的權限建議和prot參數的權限保持一致。

3.如果文件偏移量爲1000會怎樣?

偏移量必須是4K的整數倍,返回MAP_FAILED

4.mmap什麼情況下會調用失敗?

- 第二個參數:length = 0
- 第三個參數:prot
- 只指定了寫權限
- prot PROT_READ | PROT_WRITE
第5個參數fd 通過open函數時指定的 O_RDONLY / O_WRONLY

5.可以open的時候O_CREAT一個新文件來創建映射區嗎?

- 可以的,但是創建的文件的大小如果爲0的話,肯定不行
- 可以對新的文件進行擴展
- lseek()
- truncate()

6.mmap後關閉文件描述符,對mmap映射有沒有影響?

int fd = open("XXX");
mmap(,,,,fd,0);
close(fd);
映射區還存在,創建映射區的fd被關閉,沒有任何影響。

7.對ptr越界操作會怎樣?

void * ptr = mmap(NULL, 100,,,,,);
4K
越界操作操作的是非法的內存 -> 段錯誤

 

內存映射可以實現:1.進程通信;2.文件拷貝(但是不能拷貝太大的文件,一般也不用於文件拷貝)

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

 

代碼

copy.c

 1 // 使用內存映射實現文件拷貝的功能
 2 /*
 3     思路:
 4         1.對原始的文件進行內存映射
 5         2.創建一個新文件(拓展該文件)
 6         3.把新文件的數據映射到內存中
 7         4.通過內存拷貝將第一個文件的內存數據拷貝到新的文件內存中
 8         5.釋放資源
 9 */
10 #include <stdio.h>
11 #include <sys/mman.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <stdlib.h>
18 
19 int main() {
20 
21     // 1.對原始的文件進行內存映射
22     int fd = open("english.txt", O_RDWR);
23     if(fd == -1) {
24         perror("open");
25         exit(0);
26     }
27 
28     // 獲取原始文件的大小
29     int len = lseek(fd, 0, SEEK_END);
30 
31     // 2.創建一個新文件(拓展該文件)
32     int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
33     if(fd1 == -1) {
34         perror("open");
35         exit(0);
36     }
37     
38     // 對新創建的文件進行拓展
39     truncate("cpy.txt", len);
40     write(fd1, " ", 1);
41 
42     // 3.分別做內存映射
43     void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
44     void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);
45 
46     if(ptr == MAP_FAILED) {
47         perror("mmap");
48         exit(0);
49     }
50 
51     if(ptr1 == MAP_FAILED) {
52         perror("mmap");
53         exit(0);
54     }
55 
56     // 內存拷貝
57     memcpy(ptr1, ptr, len);
58     
59     // 釋放資源
60     munmap(ptr1, len);
61     munmap(ptr, len);
62 
63     close(fd1);
64     close(fd);
65 
66     return 0;
67 }

 

mmap-anon.c

 1 /*
 2     匿名映射:不需要文件實體進程一個內存映射
 3 */
 4 
 5 #include <stdio.h>
 6 #include <sys/mman.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <sys/wait.h>
14 
15 int main() {
16 
17     // 1.創建匿名內存映射區
18     int len = 4096;
19     void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
20     if(ptr == MAP_FAILED) {
21         perror("mmap");
22         exit(0);
23     }
24 
25     // 父子進程間通信
26     pid_t pid = fork();
27 
28     if(pid > 0) {
29         // 父進程
30         strcpy((char *) ptr, "hello, world");
31         wait(NULL);
32     }else if(pid == 0) {
33         // 子進程
34         sleep(1);
35         printf("%s\n", (char *)ptr);
36     }
37 
38     // 釋放內存映射區
39     int ret = munmap(ptr, len);
40 
41     if(ret == -1) {
42         perror("munmap");
43         exit(0);
44     }
45     return 0;
46 }

 

信號概述

信號

 

 

 

 

LUNIX信號一覽表

紅色信號 面試必須記住

 

 

 

 

 

 

信號的5種默認處理動作

 

 

kill、raise、abort函數

 

 

 

代碼

 1 /*  
 2     #include <sys/types.h>
 3     #include <signal.h>
 4 
 5     int kill(pid_t pid, int sig);
 6         - 功能:給任何的進程或者進程組pid, 發送任何的信號 sig
 7         - 參數:
 8             - pid :
 9                 > 0 : 將信號發送給指定的進程
10                 = 0 : 將信號發送給當前的進程組
11                 = -1 : 將信號發送給每一個有權限接收這個信號的進程
12                 < -1 : 這個pid=某個進程組的ID取反 (-12345)
13             - sig : 需要發送的信號的編號或者是宏值,0表示不發送任何信號
14 
15         kill(getppid(), 9);
16         kill(getpid(), 9);
17         
18     int raise(int sig);
19         - 功能:給當前進程發送信號
20         - 參數:
21             - sig : 要發送的信號
22         - 返回值:
23             - 成功 0
24             - 失敗 非0
25         kill(getpid(), sig);   
26 
27     void abort(void);
28         - 功能: 發送SIGABRT信號給當前的進程,殺死當前進程
29         kill(getpid(), SIGABRT);
30 */
31 
32 #include <stdio.h>
33 #include <sys/types.h>
34 #include <signal.h>
35 #include <unistd.h>
36 
37 int main() {
38 
39     pid_t pid = fork();
40 
41     if(pid == 0) {
42         // 子進程
43         int i = 0;
44         for(i = 0; i < 5; i++) {
45             printf("child process\n");
46             sleep(1);
47         }
48 
49     } else if(pid > 0) {
50         // 父進程
51         printf("parent process\n");
52         sleep(2);
53         printf("kill child process now\n");
54         kill(pid, SIGINT);
55     }
56 
57     return 0;
58 }

 

alarm函數

信號相關的函數

 

 

 

 

代碼

alarm.c

 1 /*
 2     #include <unistd.h>
 3     unsigned int alarm(unsigned int seconds);
 4         - 功能:設置定時器(鬧鐘)。函數調用,開始倒計時,當倒計時爲0的時候,
 5                 函數會給當前的進程發送一個信號:SIGALARM
 6         - 參數:
 7             seconds: 倒計時的時長,單位:秒。如果參數爲0,定時器無效(不進行倒計時,不發信號)。
 8                     取消一個定時器,通過alarm(0)。
 9         - 返回值:
10             - 之前沒有定時器,返回0
11             - 之前有定時器,返回之前的定時器剩餘的時間
12 
13     - SIGALARM :默認終止當前的進程,每一個進程都有且只有唯一的一個定時器。
14         alarm(10);  -> 返回0
15         過了1秒
16         alarm(5);   -> 返回9
17 
18     alarm(100) -> 該函數是不阻塞的
19 */
20 
21 #include <stdio.h>
22 #include <unistd.h>
23 
24 int main() {
25 
26     int seconds = alarm(5);
27     printf("seconds = %d\n", seconds);  // 0
28 
29     sleep(2);
30     seconds = alarm(2);    // 不阻塞
31     printf("seconds = %d\n", seconds);  // 3
32 
33     while(1) {
34     }
35 
36     return 0;
37 }

 

alarm1.c

 1 // 1秒鐘電腦能數多少個數?
 2 #include <stdio.h>
 3 #include <unistd.h>
 4 
 5 /*
 6     實際的時間 = 內核時間 + 用戶時間 + 消耗的時間
 7     進行文件IO操作的時候比較浪費時間
 8 
 9     定時器,與進程的狀態無關(自然定時法)。無論進程處於什麼狀態,alarm都會計時。
10 */
11 
12 int main() {    
13 
14     alarm(1);
15 
16     int i = 0;
17     while(1) {
18         printf("%i\n", i++);
19     }
20 
21     return 0;
22 }

 

setitimer定時器函數

setitimer函數

 

 

 

 

 

 

代碼

 1 /*
 2     #include <sys/time.h>
 3     int setitimer(int which, const struct itimerval *new_value,
 4                         struct itimerval *old_value);
 5     
 6         - 功能:設置定時器(鬧鐘)。可以替代alarm函數。精度微妙us,可以實現週期性定時
 7         - 參數:
 8             - which : 定時器以什麼時間計時
 9               ITIMER_REAL: 真實時間,時間到達,發送 SIGALRM   常用
10               ITIMER_VIRTUAL: 用戶時間,時間到達,發送 SIGVTALRM
11               ITIMER_PROF: 以該進程在用戶態和內核態下所消耗的時間來計算,時間到達,發送 SIGPROF
12 
13             - new_value: 設置定時器的屬性
14             
15                 struct itimerval {      // 定時器的結構體
16                 struct timeval it_interval;  // 每個階段的時間,間隔時間
17                 struct timeval it_value;     // 延遲多長時間執行定時器
18                 };
19 
20                 struct timeval {        // 時間的結構體
21                     time_t      tv_sec;     //  秒數     
22                     suseconds_t tv_usec;    //  微秒    
23                 };
24 
25             過10秒後,每個2秒定時一次
26            
27             - old_value :記錄上一次的定時的時間參數,一般不使用,指定NULL
28         
29         - 返回值:
30             成功 0
31             失敗 -1 並設置錯誤號
32 */
33 
34 #include <sys/time.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 
38 // 過3秒以後,每隔2秒鐘定時一次
39 int main() {
40 
41     struct itimerval new_value;
42 
43     // 設置間隔的時間
44     new_value.it_interval.tv_sec = 2;
45     new_value.it_interval.tv_usec = 0;
46 
47     // 設置延遲的時間,3秒之後開始第一次定時
48     new_value.it_value.tv_sec = 3;
49     new_value.it_value.tv_usec = 0;
50 
51 
52     int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
53     printf("定時器開始了...\n");
54 
55     if(ret == -1) {
56         perror("setitimer");
57         exit(0);
58     }
59 
60     getchar();
61 
62     return 0;
63 }

 

signal信號捕捉函數

 

 

 

代碼

signal.c

 1 /*
 2     #include <signal.h>
 3     typedef void (*sighandler_t)(int);
 4     sighandler_t signal(int signum, sighandler_t handler);
 5         - 功能:設置某個信號的捕捉行爲
 6         - 參數:
 7             - signum: 要捕捉的信號
 8             - handler: 捕捉到信號要如何處理
 9                 - SIG_IGN : 忽略信號
10                 - SIG_DFL : 使用信號默認的行爲
11                 - 回調函數 :  這個函數是內核調用,程序員只負責寫,捕捉到信號後如何去處理信號。
12                 回調函數:
13                     - 需要程序員實現,提前準備好的,函數的類型根據實際需求,看函數指針的定義
14                     - 不是程序員調用,而是當信號產生,由內核調用
15                     - 函數指針是實現回調的手段,函數實現之後,將函數名放到函數指針的位置就可以了。
16 
17         - 返回值:
18             成功,返回上一次註冊的信號處理函數的地址。第一次調用返回NULL
19             失敗,返回SIG_ERR,設置錯誤號
20             
21     SIGKILL SIGSTOP不能被捕捉,不能被忽略。
22 */
23 
24 #include <sys/time.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <signal.h>
28 
29 void myalarm(int num) {
30     printf("捕捉到了信號的編號是:%d\n", num);
31     printf("xxxxxxx\n");
32 }
33 
34 // 過3秒以後,每隔2秒鐘定時一次
35 int main() {
36 
37     // 註冊信號捕捉
38     // signal(SIGALRM, SIG_IGN);
39     // signal(SIGALRM, SIG_DFL);
40     // void (*sighandler_t)(int); 函數指針,int類型的參數表示捕捉到的信號的值。
41     signal(SIGALRM, myalarm);
42 
43     struct itimerval new_value;
44 
45     // 設置間隔的時間
46     new_value.it_interval.tv_sec = 2;
47     new_value.it_interval.tv_usec = 0;
48 
49     // 設置延遲的時間,3秒之後開始第一次定時
50     new_value.it_value.tv_sec = 3;
51     new_value.it_value.tv_usec = 0;
52 
53     int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
54     printf("定時器開始了...\n");
55 
56     if(ret == -1) {
57         perror("setitimer");
58         exit(0);
59     }
60 
61     getchar();
62 
63     return 0;
64 }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章