linux:进程之间的通信
ipc :進程間通信(InterProcess Communication)
1、管道?
同一時間是單向的:父讀子寫,或父寫子讀?
管道中的數據 ,讀走就沒了
?參數是一個整型數的數組,數組的大小是兩個,返回值是一個整型數
pipe函數可以建立一個管道
我們不確定到底是父進程先運行還是子進程先運行
如果是子進程先運行的話,管道里面沒有東西,孩子讀取read 會堵塞,cpu就會被父親爭奪到,父親就會關閉讀端,往管道里面寫
關閉讀端并不是把整個管道關閉了
1-a(無名管道)
看一下man 2 pipe的用法
包含一個頭文件
創建管道的原型
因為管道是基于父子進程之間的通信,所以用到fork
他的頭文件,它不需要傳參的
fork 有個返回值? int pid;
fork 以后 父進程會拷貝一個空間給子進程,包括pipe 出來的fd,所以他們的管道是同一個管道,
因為這個fd 指向內核中的管道位置都是一樣的
系統調用在man 手冊的第二章節
代碼如下:
分析一下代碼 ,main函數進來,調用pipe創建管道,pipe 需要一個變量fd,fd是個數組
創建成功后調用子進程
pid <0代表子進程創建失敗
pid>0代表進入父進程的空間,父親負責寫,fd[0]是讀端,fd[1]是寫入端
(父進程提前退出了),父進程可以等待子進程退出,子進程也可以意思一下
我們不知道fork 以后父子進程誰先運行,假設我們想讓子進程先運行,那就讓父親上來先睡個三秒
因為管道里面沒有數據,所以會先阻塞
無名管道的意思:pipe出來? ? ? 我們只能拿到讀端和寫端,我們并不知道管道的名字是什么
并且他只是存活在內存當中,在磁盤上面除了我們編寫的代碼和程序以外就沒有其他的文件啦,
無名管道不以文件的形式存在磁盤當中的
1-b命名管道
?
?第一個參數是管道名
第二個參數是mode_t
mkfifo 在main手冊的第三頁
如何創建管道呢?? ./加上文件名
./加上文件名
./加上文件名
0600可讀可寫的方式
? ? ? ? ? ?????????????????????????????????????????
?代碼如下
運行的結果:生成了一個file
ls -l 列出文件的詳細信息,如創建者,創建時間,文件的讀寫權限列表等等
file是一個p類型的,可讀可寫
然后我們可以打印一些調試信息,根據返回值
?????????
????????
失敗的原因:文件已經存在
路徑文件已經存在,返回這個錯誤
看看perror 能不能找出來錯誤
文件存在
?
我們不希望文件存在的時候會出錯,我們可以這樣做
不會隨意提示我們失敗了還是其他的
我們先創建這個文件 ,如果mkfifo("./file",0600) =0? 代表創建成功,條件不滿足,執行下一條
如果文件存在mkfifo("./file",0600)就會變成 -1
還能在加點判斷
如果file 不存在的話,運行一下
如果再運行呢?
?
為了避免打印一些錯誤的亂七八糟的錯誤消息
?
運行結果
? ? 1-c命名管道的數據通信編程實現
?如果file 有的話不做任何事,file 沒有的話創建file
去打開這個管道
?
包含頭文件
?
我成功運行了 ,但是程序結束不了,沒有任何的輸出,也就是說 我打開管道,下面的那一句沒辦法執行
?
寫的話就不創建fifo啦
先運行read 會阻塞
然后再調用write
?
這個時候其他進程打開,他才會往下走
?
那么如何使用FIFO呢?
?先修改一下read.c,從fd 里面讀,讀到buf,一次讀30個字節
再修改write.c,往fd里面寫
卡在這啦
?然后運行write
讀了17個,內容是:message from fifo
?
?
這就實現了兩個進程之間的通信
那write可不可以一直寫呢?
防止速度過快,讓他每秒寫一下
?
然后就可以實現一直發,一直收
?
一般呢,都是讓他阻塞,不阻塞就失去意義啦
?我們試著用非阻塞的方式打開
?
有點怪怪的感覺
?
?
管道的目的就是做數據交互
2,消息隊列
因為它是一個鏈表,鏈表中每一個消息應該是一個結構體,因為鏈表的每一項節點,就是一個結構體
每一個隊列都有消息,消息有類型和內容
A能獲取B的數據,或者B能獲取A的數據
步驟:
獲取一個隊列
讀隊列
另一個:
獲取一個隊列
寫數據到隊列
創建隊列:msg隊列的意思,get創建或者打開的意思
key是一個索引值,我們通過這個索引值,在內核中去找到某個隊列
flag是打開隊列的方式
最終返回一個int型的類型,int型是隊列的id
A跟B都要通過一個key在內核當中找到或者創建一個隊列
所以這個key,A應該知道,B也應該知道,A,B用的key應該是一樣的
第二種情況是私有的
如果
key不一樣,在隊列里面找不到同一個隊列
發送消息:
第一個是對列的ID啦,第二個是指針,第三個是消息的大小,第四個flag是打開隊列的方式
?
?
讀取消息:
第一個是對列的ID啦,第二個是消息的放的地方,第三個是放多大,第四個是隊列的類型,,第四個flag是打開隊列的方式
(你要讀取哪個節點可以根據類型來找)
關閉隊列(或者說對列的其他操作):
2-b用消息隊列API,實現消息隊列的消息共享的編程
第一步肯定是獲取
?
?再根據?
有的話直接獲取。沒有的話直接創建,再加上隊列的權限,0777(可讀,可寫,可執行)
msgget的返回值是一個整型數
讀隊列如何讀呢?
?
?我們看一下接收:
第一個參數是隊列的id
第二個參數是我們的消息
第三個參數是消息的長度
第四個參數是消息的類型
第五個參數是默認的值
一般我們推薦msgbuf 的結構體這樣寫,里面有兩項內容,一項是我們想要的mtype,另一項是我們真正的內容,內容一個不夠,一般可以128個字節
?
read怎么讀呢?read肯定要讀到這個buf 里面來
所以要定義一個Buf
?要把readBuf 的值,放進來
?讀多大呢?一個結構體的大小(mtest)
????????????????????????
?消息的類型呢?(mtest)
????????????????
再來默認的值
以默認的方式,接受消息,讀不到888類型的話,會一直阻塞,知道讀到為止(mtest)
把讀到的消息隊列打出來
完整讀代碼?
給他換個名字
編輯完讀的,我們再來編輯寫的文件
發送我們要構造一個buf, sendbuf是一個結構體
然后通過同樣的索引值找到消息隊列?
?
找到以后調用msgsnd
第一個參數,是消息隊列的id
第二個參數是你要發送的消息隊列,是一個地址
第三個參數,是你要發送內容的大小
????????
?代碼實現
0是非阻塞的方式
?完整寫的代碼
????????
?
運行一下get ,get 阻塞在這邊
這句話沒打印,說明消息隊列已經有了
?
發送一下數據
?
對比一下
?
(qall?退出)
如果說這讓兩個隊列互相通信呢?
我發完以后再讀咋辦?
???????(從msgId里面讀,讀到readBuf里面,讀的長度sizeof(readBuf.mtext),讀的消息類型988,
讀完以后再把數據發出來。)
你收到別人的數據,是不是還要給別人發回去?
?
?具體操作如下
先是堵塞
?
?
?
(補充:可以查看歷史指令)
?
2-c 鍵值生成以及消息隊列移除
?函數原型:第一個是路徑名,第二個是一個整數,返回值是key_t
.代表當前路徑,1代表隨便小的一個數
一般來說,通過路徑名以及這個1配合會生成key值
?那路徑名放在這邊,用的是路徑的什么?
用的是路徑的索引節點
?當前路徑的索引節點是多少?
查詢文件索引節點號的方法是: ls -i
.代表當前文件
兩者配合使用
?
修改一下之前的代碼
你的id值隨便,寫個 23 可以,寫個字符‘z’ 也可以
key以16進制打出來
另一個也改一下,都是用的 . 加上'z'
?
key的值一樣,代表在內核里面找到的消息隊列是同一個隊列,就可以用這個隊列進行通訊
?
?
每兩個進程,通過一個消息隊列,去做通訊,我們通過key來找
當我們找的key在內存里面沒有這個隊列,我們會去創建一個新隊列,這樣的事做多了,導致內核當中這個隊列很多,有沒有辦法,用完隊列就給他干掉?
第一個是隊列的id
第二個是指令
第三個是一般寫個NULL;
指令有哪些指令呢?
有下面四個
一般用的最多的IPC_RMID,它的意思是把消息隊列的鏈表,從內核中移除
修改代碼
?(補充:有時候打完代碼,異常退出導致生成了swp文件,如何辦?把swp文件刪掉就好啦)
一打開文件就這樣了
看到那個q!沒有
rm ? .文件名.c.swp??
?
?然后就可以正常使用了
3、共享內存
1.創建共享內存/打開? ? ??shmget
2.映射? ? ? ? ? ?shmat
3.數據的交換? ? ? ??
4.釋放共享內存? ? ?shmdt
5.干掉? ? ??shmctl
如果A里面有個指針,剛好指向共享內存(int *p = 共享內存)
這個共享內存雖然不在A的存儲空間,但是A可以掛載上來,把里面的內容直接打出來(printf("%s",p) )
B也是一樣的
如何寫入呢?
就和操作普通字符串一樣,用strcpy把想寫的話,寫入共享內存
3-b共享內存編程實現
1.創建共享內存/打開? ? ??shmget
2.映射? ? ? ? ? ?shmat
3.數據的交換? ? ? ??
4.釋放共享內存? ? ?shmdt
5.干掉? ? ??shmctl
共享內存的API
創建共享內存/打開
第一個參數是一個key,跟消息隊列的key一樣
注意第二個:共享內存的大小必須以兆對其的
第三個:假如我們寫的時候,創建一個共享內存,shmflg可以配合IPC_CREAT,代表創建內存,共享內存必須得有權限
?成功返回共享內存的id,失敗返回-1
?
exit正常退出返回0 ,異常返回-1
映射? ?
第一個獲取共享內存的id
第二個一般寫0,讓linux 內核自動為我們安排共享內存
第三個一般也寫0,0代表映射進來的內存,可讀可寫,除非你不想讓你的內存具備可讀可寫的屬性,可以加一些東西
?
at會把共享內存掛載到進程的存儲空間,那么進程的存儲空間里面,我們定義一個變量,指向共享內存,一般定義一個指針
共享內存映射完,就可以操作了,用strcpy給內存地址賦值
然后去卸載共享內存
(
上面的at 那個第一個是id
第二個是0
第三個也是0
)
直接把映射的地址放進來
掛掉共享內存,把整個共享內存都去掉,防止一直占用內存
第一個是id
第二個是指令
第三個是一般寫0,這個buf用來存放卸載內存的時候,產生的一些信息,我們不關心這些信息
這些信息有什么呢?(時間呀,大小呀)
?
那cmd 有哪些指令呢?(消息隊列刪除的時候,用的也是IPC_RMID)
關于寫的完整代碼
寫的寫完了,然后我們來讀
讀的時候我們只要獲取,不創建
那面刪了,這面就不刪了(A干掉共享內存,希望B程序退出以后再干掉,所以A多了5秒sleep)
關于讀的完整代碼
如何查看系統當中有哪些共享內存?
ipcs -m
我們去修改一下代碼,變成創建共享內存
?加了第20 行
創建的共享內存大小是4兆,并沒有設備去連接它(nattch)
?
那么如何把共享內存刪掉呢?
ipcrm -m 加上shmid
(拓展:如果A ,B都往共享內存里面寫,數據會出現一些問題,可不可以A寫的時候B不可以寫,解決的辦法就是信號量來控制)
4、信號
?在系統級的應用當中,信號0被占用
對我們信號層來說,信號是從1到64的
?查看系統中的所有信號
kill -l
?(1)SIGHUP? ? 掛起的意思
(2)SIGINT? ? ?中斷的意思? ? ?就是ctrl +c 的信號
(3)SIGQUIT? ?退出
(4)SIGILL? ? ? 出現的問題
(6)SIGABRT? ? 丟棄
(7)SIGBUS? ?總線信號
(9)SIGKILL? 殺死進程
(14)SIGALRM? 鬧鐘信號
(19)SIGSTOP? ?停止信號
(29)SIGIO? ? ?IO口的訪問
?
查找?
ps -aux|grep a.out
?
可以 kill -9 加上id 號
或者kill -SIGKILL 加上id號
所謂的實現異步通訊的手段 就是?捕獲信號
4-b信號編程
?
這做的是信號的綁定
我們先來用信號的捕捉:
現在想實現一種效果,鍵盤按下ctrl +c 沒辦法停止我
這是一個函數指針,返回值是void 型,它的參數是一個整型數,中間括號是函數名
它的返回值是 signal_t
它的第一個參數就是我們說的信號(那64個),要捕捉的信號
第二個參數就是上面的結構體指針了,sighandler_t,_t就是結構體的意思,hanlder就是一個函數指針,指向這種形式的void (*sighandler_t)(int)函數
?
?
具體的代碼
?按一下ctrl +c,獲取到的signum是2,2不就是SIGINT,
因為系統收到的默認動作應該是終止我的進程,那我現在捕捉信號,修改默認動作,讓他執行,我希望它執行的函數
殺死它
?
我們還可以注冊其他的信號
這邊的kill不是殺死,是發指令的意思
?
?
雖然對他修改可還是改不動
?
我們現在都是通過鍵盤,通過這個指令給它發信號,我能不能寫個程序對他完成信號的發送?
用到的函數是kill函數,
main函數啟動以后去殺進程,怎么殺呢?我希望傳遞一個信號值,以及pid 號,所以我要傳參
?我們char 進來的是字符串,signum 和pid 是整型數,所以對他做一下轉化
?我們做一下測試,信號值是9,pid 的值是2345
如果你沒有轉化的話,9和2345是字符串呀
我們看一下如何發送信號
一個是進程號,一個是signum
先運行之前那個程序
找到它對應的pid 號
?
?我們想要發送的是9
?
?這就是通過軟件編程實現,捕捉信號以后對他的信號值裝配
第一個函數是信號的編號,第二個是函數處理的函數指針
?
那我們如何發信號呢?要注意參數傳進來,要進行轉化,然后調用kill指令,發送指令
?
?還有一種寫法,調用system
要構造這么一串字符串
?有個函數叫sprintf
第一個參數,是我們需要構造的目標字符串
第二個參數,是我們要做出來的字符串的長相
用system調用腳本來殺
?
?
那么如何忽略信號?用這個宏就可以
?找到那個文件
?
?
ctrl+c 被忽略了
?
那么我們試著殺它,能不能忽略
殺死它不能被忽略
?
?
?sigaction 的函數原型
第二個參數是一個結構體
第三個參數用來做備份的
sigcation里面的
第一個函數指針1
第二個函數指針2
第三個 mask結構體,起到阻塞的作用
第四個參數是一個整型數 ,有一個標記
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);struct sigaction {void (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用sigset_t sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。int sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據}; //回調函數句柄sa_handler、sa_sigaction只能任選其一?
?
siginfo_t {int si_signo; /* Signal number */int si_errno; /* An errno value */int si_code; /* Signal code */int si_trapno; /* Trap number that causedhardware-generated signal(unused on most architectures) */pid_t si_pid; /* Sending process ID */uid_t si_uid; /* Real user ID of sending process */int si_status; /* Exit value or signal */clock_t si_utime; /* User time consumed */clock_t si_stime; /* System time consumed */sigval_t si_value; /* Signal value */int si_int; /* POSIX.1b signal */void *si_ptr; /* POSIX.1b signal */int si_overrun; /* Timer overrun count; POSIX.1b timers */int si_timerid; /* Timer ID; POSIX.1b timers */void *si_addr; /* Memory location which caused fault */int si_band; /* Band event */int si_fd; /* File descriptor */ }?
第一個參數發給誰
第二個發的是什么信號
第三個就是我們要的消息
?
精彩博客:
Linux 信號(signal) - 簡書 (jianshu.com)
。
4-c信號攜帶消息,編程實戰
等會需要調用這個函數
?
?
?
?假設我想捕獲SIGUSR1,
第二個參數是我想干嘛
第三個參數作為備份
這就完成了sigaction的調用
?現在要做的就是把第二個參數做出來,
要配置sigaction是誰
要接收數據flags是誰
?作用:獲得消息
?
?把地址傳進來
?函數如何處理呢?
?我們給他補充完整
?siginfo_t 有多少內容呢?
?
這也是以一個結構體
?si_value也是一個結構體,也可以把pid 號拿出來
#include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval value); union sigval {int sival_int;void *sival_ptr;};思路;
首先注冊信號
第一個是收哪個信號
第二個參數是我想干嘛
第三個參數作為備份
?第二個參數要受到信號就要指定里面的flag
?收到信號調用handler處理信號
?
hanlder 收到信號把信號的值打出來,接著把內容打出來,內容是否有取決于context,context非空的話,我內容就有,我們把info里面si_int的數據打出來
完整的代碼
?
?接著我們發信號
用到的函數
第一個是信號的pid 號?
? ? ? ?第三個是一個聯合體
?
完整的代碼
?
運行結果
?
?
?那我們是不是可以把pid 號打出來呀?是不是也可以把發送者的pid 也打出來
?
?
那我們再來修改一下send.c那個程序
send把我自己的pid 也打出來對比一下
運行結果
?
?
誰發的,發的內容是什么,都知道(可以發送整型數,也可以發送字符串)
?
?5、信號量
信號量用來管控臨界資源
?信號量集:
p 操作,拿鎖
v操作,放回鎖
?
?5-b信號量編程實現
第一個key 按消息隊列和共享內存的經驗
第二個信號量集中的信號量的個數
第三個IPC_CREAT(熟悉不?不熟悉看之前的)
?
?
初始化
?
第一個參數是我們操作的信號量集 的id
第二個參數代表你要操作第幾個信號量
第四個參數定義一個聯合體
?
?cmd:SETVAL設置信號量的初值
?聯合體的作用?
?
具體思路 :main 函數進來獲取/創建一個信號量
然后對信號量初始化操作
接著我們去創建進程(代碼還沒寫完)
?
?運行情況
?能不能讓兒子先運行,父親在運行呢?
(用到了p? v操作)
取鑰匙:
第一個參數是id
第二個參數是配置的信號量(是一個指針,指向一個數組)
? ? ? ? 第三個是第二項的個數
?????????
?完整的代碼
思路:
main 函數調用semget 創建了一個信號量,這個信號量是一個信號量集合,只有一個信號量
再調用semclt 初始化這個信號量,只有一個信號量
信號量(當成鎖)初始化為0
再用frok去創建子進程
子進程打印一句話,然后把鎖放進去,也就是修改信號量
即使父親先運行,沒有”鎖“,也只能卡那,必須等兒子給他,他才能運行
?
?運行的結果
?如果銷毀鎖呢?
調用semctl()
這個他說可以三個參數,可以四個參數,銷毀的時候三個就夠(這個IPC_RMID是不是似曾相識?)
?
總結
以上是生活随笔為你收集整理的linux:进程之间的通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解压iso到u盘怎么做 ISO文件如何解
- 下一篇: linux:线程