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 }