【Linux系統編程學習】匿名管道pipe與有名管道fifo

此為牛客Linux C++和黑馬Linux系統編程課程筆記。

0. 關于進程通信

Linux環境下,進程地址空間相互獨立,每個進程各自有不同的用戶地址空間。任何一個進程的全局變量在另一個進程中都看不到,所以進程和進程之間不能相互訪問,要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信(IPC,InterProcess Communication)。

在進程間完成數據傳遞需要借助操作系統提供特殊的方法,如:文件、管道、信號、共享內存、消息隊列、套接字、命名管道等。隨著計算機的蓬勃發展,一些方法由于自身設計缺陷被淘汰或者棄用。現今常用的進程間通信方式有:
① 管道 (使用最簡單)
② 信號 (開銷最小)
③ 共享映射區 (無血緣關系)
④ 本地套接字 (最穩定)

1. 匿名管道

管道是一種最基本的IPC機制,作用于有血緣關系的進程之間,完成數據傳遞。調用pipe系統函數即可創建一個管道。有如下特點:
在這里插入圖片描述
在這里插入圖片描述
匿名管道采用了循環隊列,可將寫指針看作隊列頭,讀指針看作隊列尾:
在這里插入圖片描述

2. pipe函數

Linux中使用pipe函數創建管道:

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

功能:
創建一個匿名管道,用于進程間通信。

參數:
int pipefd[2] 這個數組是一個傳出參數。
pipefd[0] 對應的是管道的讀端
pipefd[1] 對應的是管道的寫端

返回值:
成功 0
失敗 -1

注意: 管道默認是阻塞的:如果管道中沒有數據,read阻塞,如果管道滿了,write阻塞匿名管道只能用于具有關系的進程之間的通信(父子進程,兄弟進程)

當調用pipe后,當前進程的文件描述符表中就已經有兩個文件描述符分別指向管道的讀端和寫端,pipefd[0]和pipefd[1]返回這兩個文件描述符。

如下示例程序能夠實現:子進程發送數據給父進程,父進程讀取到數據輸出到終端。

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{// 子進程發送數據給父進程,父進程讀取到數據輸出int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe error");exit(0);}pid_t pid = fork();if(pid > 0) {char buffer[1024] = {0};read(pipefd[0], buffer, sizeof(buffer)); // 如果管道為空,此處阻塞printf("recieved : %s", buffer);} else if(pid == 0) {char* content = "hello, im child process";write(pipefd[1], content, strlen(content));}return 0;
}

創建管道,使用read和write分別在pipefd[0]中讀數據,在pipefd[1]中寫數據。

運行結果如下:
在這里插入圖片描述
可見父進程中收到了子進程中傳遞的消息。

3. pipe管道的讀寫特點:

使用管道時,需要注意以下幾種特殊的情況(假設都是阻塞I/O操作)

1.所有的指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),有進程從管道的讀端
讀數據,那么管道中剩余的數據被讀取以后,再次read會返回0,就像讀到文件末尾一樣。

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

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

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

總結:
????讀管道:
???????? 管道中有數據,read返回實際讀到的字節數。
???????? 管道中無數據:
???????????????? 寫端被全部關閉,read返回0(相當于讀到文件的末尾)
??????????????? ?寫端沒有完全關閉,read阻塞等待
????寫管道:
????????管道讀端全部被關閉,進程異常終止(進程收到SIGPIPE信號)
????????管道讀端沒有全部關閉:
????????????????管道已滿,write阻塞
????????????????管道沒有滿,write將數據寫入,并返回實際寫入的字節數

4. 有名管道

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

5. mkfifo函數

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

參數:

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

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

6. 有名管道的注意事項:

1.一個為只讀而打開一個管道的進程會阻塞,直到另外一個進程為只寫打開管道;
2.一個為只寫而打開一個管道的進程會阻塞,直到另外一個進程為只讀打開管道

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

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

7. 使用FIFO實現簡單的聊天功能

chatterA.c:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 第一步,首先判斷有名管道是否存在int ret = access("fifo1", F_OK);if(ret == -1) {// 說明管道文件不存在,則創建管道printf("管道不存在,創建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {// 如果創建管道失敗perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}// 第二步,以只寫的方式打開fifo1,以只讀的方式打開fifo2// fifo1管道負責chatter1寫chatter2讀// fifo2管道負責chatter1讀chatter2寫int fd1 = open("fifo1", O_WRONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打開fifo1成功,等待寫入...\n");int fd2 = open("fifo2", O_RDONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打開fifo2成功,等待讀取...\n");// 第三步,循環地往管道fifo1里寫入數據char buffer[1024] = {0};while(1) {// 把buffer置空以便重復寫入memset(buffer, 0, 1024); // 獲取標準輸入的數據fgets(buffer, 1024, stdin);ret = write(fd1, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}// 第四步,循環地從管道fifo2中讀出數據memset(buffer, 0, 1024);ret = read(fd2, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterB傳來消息: %s\n", buffer);}close(fd1);close(fd2);return 0;
}

chatterB:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main()
{// 與chatterA完全對稱,往fifo2中寫,從fifo1中讀int ret = access("fifo1", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo1", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}ret = access("fifo2", F_OK);if(ret == -1) {printf("管道不存在,創建管道\n");ret = mkfifo("fifo2", 0664);if(ret == -1) {perror("mkfifo");exit(0);}}int fd1 = open("fifo1", O_RDONLY);if(fd1 == -1) {perror("open");exit(0);}printf("打開fifo1成功,等待讀取...\n");int fd2 = open("fifo2", O_WRONLY);if(fd2 == -1) {perror("open");exit(0);}printf("打開fifo2成功,等待寫入...\n");char buffer[1024] = {0};while(1) {memset(buffer, 0, 1024);ret = read(fd1, buffer, 1024);if(ret <= 0) {perror("read");exit(0);}printf("chatterA傳來消息: %s\n", buffer);memset(buffer, 0, 1024); fgets(buffer, 1024, stdin);ret = write(fd2, buffer, strlen(buffer));if(ret == -1) {perror("write");exit(0);}}close(fd1);close(fd2);return 0;
}

在兩個終端中分別運行程序:
chatterA:
在這里插入圖片描述
chatterB:
在這里插入圖片描述
在運行chatterA的終端中輸入:hello,im chatterA ,回車

chatterB:
在這里插入圖片描述
輸出了chatterA中傳來的信息,同樣,在運行chatterB的終端中輸入:hello,im chatterB ,回車

chatterA:
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384562.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384562.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384562.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Linux系統編程學習】信號、信號集以其相關函數

此為牛客Linux C和黑馬Linux系統編程課程筆記。 文章目錄0. 信號的概念1. Linux信號一覽表2. 信號相關函數3. kill函數4. raise函數5. abort函數6. alarm函數7. setitimer函數8. signal函數9. 信號集10. 自定義信號集相關函數11. sigprocmask函數12. sigpending函數13. sigacti…

【Linux系統編程學習】父進程捕獲SIGCHLD信號以處理僵尸進程

配合之前說過的sigaction函數和waitpid函數&#xff0c;我們可以解決子進程變成僵尸進程的問題。 先看如下示例程序&#xff1a; #include <sys/time.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> …

【Linux系統編程學習】Linux線程控制原語

此為牛客Linux C課程筆記。 0. 關于線程 注意&#xff1a;LWP號和線程id不同&#xff0c; LWP號是CPU分配時間片的依據&#xff0c;線程id是用于在進程內部區分線程的。 1. 線程與進程的區別 對于進程來說&#xff0c;相同的地址(同一個虛擬地址)在不同的進程中&#xff0c;反…

【Linux網絡編程學習】預備知識(網絡字節序、IP地址轉換函數、sockaddr數據結構)

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 網絡字節序 我們已經知道&#xff0c;內存中的多字節數據相對于內存地址有大端和小端之分。 磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分。網絡數據流同樣有大端小端之分&#xff0c;那么如何定義網絡數…

【Linux網絡編程學習】socket API(socket、bind、listen、accept、connect)及簡單應用

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 什么是socket 所謂 socket&#xff08;套接字&#xff09;&#xff0c;就是對網絡中不同主機上的應用進程之間進行雙向通信的端點的抽象。 一個套接字就是網絡上進程通信的一端&#xff0c;提供了應用層進程利用網絡協議交換…

【Linux網絡編程學習】使用socket實現簡單服務器——多進程多線程版本

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 多進程版 1.1 思路 大體思路與上一篇的單進程版服務器–客戶端類似&#xff0c;都是遵循下圖&#xff1a; 多進程版本有以下幾點需要注意&#xff1a; 由于TCP是點對點連接&#xff0c;服務器主進程連接了一個客戶端以后…

【Linux網絡編程學習】I/O多路復用——select和poll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 0. I/O多路復用 所謂I/O就是對socket提供的內存緩沖區的寫入和讀出。 多路復用就是指程序能同時監聽多個文件描述符。 之前的學習中寫了多進程和多線程版的簡單服務器模型&#xff0c;但是有個問題&#xff1a;每次新來一個客…

【Linux網絡編程學習】I/O多路復用——epoll

此為牛客Linux C課程和黑馬Linux系統編程筆記。 1. 關于epoll epoll是Linux下多路復用IO接口select/poll的增強版本&#xff0c;它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率&#xff0c;因為它會復用文件描述符集合來傳遞結果而不用迫使開發者每次…

【Linux網絡編程學習】阻塞、非阻塞、同步、異步以及五種I/O模型

文章目錄1. 基本概念1.1 阻塞與非阻塞1.2 同步與異步1.3 為什么沒有“異步阻塞”2. 五種IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO復用&#xff08;IO multiplexing&#xff09;2.4 信號驅動&#xff08;signal-driven&#xff09;2.5 異步&#xff08;asynchron…

LRU緩存 數據結構設計(C++)

做LeetCode第146題LRU緩存&#xff0c;覺得收獲不小&#xff0c;特此記錄。 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存。int get(int key) 如果關鍵…

STM32時鐘樹解析

本人之前其實也用STM32做過一些小東西&#xff0c;但因為時鐘的初始化一般是直接在SystemInit時鐘系統初始化函數里直接配置為72MHz&#xff0c;所以對于STM32的時鐘框圖并沒有怎么理會&#xff0c;今天剛好有空就重新看了一下并寫一篇博客記錄一下吧&#xff0c;以免以后又忘了…

S3C2440時鐘體系

S3C2440在默認情況下&#xff0c;整個系統全靠一個12MHz的外部晶振提供頻率來工作運行的&#xff0c;也就是說CPU、內存、UART、ADC等所有需要用到時鐘頻率的硬件都工作在12MHz下&#xff0c;但是通過查閱芯片手冊我們知道CPU時鐘最高可為400MHZ&#xff0c;那么怎么設置時鐘讓…

關于MCU、CPU擴展SDRAM的一個小知識

像上圖這種硬件電路圖上的16個數據位和我們在初始化SDRAM的時候設置的16位數據位寬是指我們讀寫SDRAM的時候可以同時讀寫16個數據位&#xff0c;數據線越多肯定越快&#xff0c;但是數據線也不可能無限增加&#xff0c;我們在程序里是可以讀寫8位&#xff0c;16位&#xff0c;3…

S3C2440擴展SDRAM

本文主要目的是記錄一下S3C2440擴展SDRAM的一些知識&#xff0c;方便以后查閱。 通過查閱手冊我們知道&#xff0c;2440有8個可以用來擴展內存的BANK&#xff0c;其中第6和第7還可用來擴展SDRAM 下面我們來看一下2440擴展SDRAM需要設置哪些寄存器。 一、BWSCON寄存器 該寄存器…

匯編語言的相對跳轉和絕對跳轉以及反匯編代碼解析

上圖第一行的b1 main為相對跳轉&#xff0c;即跳轉到pcoffset,其中pc為當前pc值&#xff0c;offset可以理解為偏移地址&#xff0c;也就是根據當前所在地址加上偏移地址實現跳轉&#xff0c;為相對跳轉。 我們來看看它的反匯編代碼 上圖清除完bss區后使用b1指令跳轉到30000668…

韋東山嵌入式第一期14課第004節_und異常模示程序示例_P筆記

本節課的第一個程序韋老師是想讓大家見識一下未定義異常&#xff0c;而第二個程序是對第一個程序進行改進&#xff0c;防止在某些條件下執行不了&#xff0c;下面就來講一下第2個程序改進了哪些地方并且有什么用。 程序在此路徑中&#xff1a;源碼文檔圖片\源碼\源碼_20180321…

關于NOR FLASH地址左右移的問題

問題引入&#xff1a;不知道你會不會有這樣的疑問&#xff1a;為什么在發送解鎖命令時&#xff0c;我們不用右移一位&#xff0c;而發送扇區地址時卻要右移一位&#xff08;nor_cmd函數內部已經左移一位&#xff09;&#xff0c;這里先補充說明一下說明是cpu角度和nor角度&…

在linux下利用ls命令進行模糊查找

如上圖&#xff0c;我們當前路徑下有三個文件&#xff0c;分別為helloworld.c以及helloworld和1.c&#xff0c;直接輸入命令ls則顯示所有文件&#xff0c;我們可以利用ls 加*的方向進行模糊查找。 輸入ls 目錄名 形式的命令行&#xff0c;則是對該目錄名下的文件全部進行顯示&a…

Makefile常見符號意思

Makefile里有許許多多的符號&#xff0c;對于新手而言如果沒有經常使用&#xff0c;就很容易忘記&#xff0c;所以我把常見符號的意義寫下&#xff0c;方便日后忘記查詢。本文章會持續更新... 1.$&#xff1a;代表目標&#xff1b;$^代表所有依賴&#xff0c;$^代表第一個依賴。…