Linux進程間通信--進程,信號,管道,消息隊列,信號量,共享內存
Linux進程間通信--進程,信號,管道,消息隊列,信號量,共享內存
參考:《linux編程從入門到精通》,《Linux C程序設計大全》,《unix環(huán)境高級編程》
參考:C和指針學習?
說明:本文非常的長,也是為了便于查找和比較,所以放在一起了
Linux 傳統(tǒng)的進程間通信有很多,如各類管道、消息隊列、內存共享、信號量等等。但它們都無法介于內核態(tài)與用戶態(tài)使用,原因如表
通信方法無法介于內核態(tài)與用戶態(tài)的原因管道(不包括命名管道)局限于父子進程間的通信。消息隊列在硬、軟中斷中無法無阻塞地接收數(shù)據(jù)。信號量無法介于內核態(tài)和用戶態(tài)使用。內存共享需要信號量輔助,而信號量又無法使用。套接字在硬、軟中斷中無法無阻塞地接收數(shù)據(jù)。
一.進程
1.進程表
ps顯示正在運行的進程
# ps -ef
TIME 進程目前占用的cpu時間,CMD顯示啟動進程所使用的命令
#ps ax
STAT表明進程的狀態(tài)
S 睡眠,s進程是會話期首進程;R 運行;D 等待;T 停止;Z 僵尸;N 低優(yōu)先級任務,nice;W 分頁;
+進程屬于前臺進程組;l 進程是多線程;<高優(yōu)先級任務
#ps -l ?或#ps -al
表現(xiàn)良好的程序為nice程序,系統(tǒng)根據(jù)進程的nice值決定他的優(yōu)先級
-f是長格式
2.父子進程id
pid當前進程的;
uid當前進程的實際用戶
eid當前進程的有效用戶
#include
運行結果:
3.設置進程組id以及進程sleep
setpgid使當前進程為新進程組的組長
#include
說明:setpgid(0,0)等價于setpgrp(0,0)
setpgid(0,0)第1個參數(shù)用于指定要修改的進程id。如果為0,則指當前進程。第2個參數(shù)用于指定新的進程組id。如果為0,則指當前進程。
先運行程序
#./example13_2
再查看進程
#ps alef
#ps -ao pid,pgrp,cmd|grep 13_2 ?
或者
#ps -ao pid,pgrp,cmd
4.子進程
fork為0說明是父進程
#include
輸出
fork...
AA
注意:?
警告: 隱式聲明與內建函數(shù) ‘exit’ 不兼容?
警告: 隱式聲明與內建函數(shù) ‘sprintf’ 不兼容???
警告: 隱式聲明與內建函數(shù) ‘printf’ 不兼容
加入這兩個頭文件就可以了!
#include
#ps -ao pid,pgrp,cmd
3165就是子進程
#ps alef
5.進程會話
setsid的調用進程應該不是某個進程組的組長進程;
setsid調用成功后生成新會話,新會話id是調用進程的進程id;
新會話只包含一個進程組一個進程即調用進程,沒有控制終端。
setid主要是實現(xiàn)進程的后臺運行
#include
修改后的程序
#include
父進程必須調用wait等待子進程推出,如果沒有子進程退出exit,則wait進入阻塞!
6.進程的控制終端
#tty
在secureCRT中觀看其他的會輸出
/dev/pts/1等依次類推
#ps -ax
查看進程的控制終端
有列tty的就是控制終端,有值表明進程有控制終端,無則表明是后臺進程。
延伸:php的POSIX 函數(shù)以及進程測試
7.進程的狀態(tài)
可運行;
等待;
暫停;
僵尸;
進程在終止前向父進程發(fā)送SIGCLD信號,父進程調用wait等待子進程的退出!
如果,父進程沒有調用wait而子進程已經退出,那么父進程成為僵尸進程;
如果,父進程沒有等子進程退出自己已經先退出,那么子進程成為孤兒進程;
通過top命令看到
8.進程的優(yōu)先級
優(yōu)先級數(shù)值越低,則優(yōu)先級越高!
優(yōu)先級由優(yōu)先級別(PR)+進程的謙讓值(NI) ?聯(lián)合確定。
PR值是由父進程繼承而來,是不可修改的。
Linux提供nice系統(tǒng)調用修改自身的NI值;setpriority系統(tǒng)調用可以修改其他進程以及進程組的NI值。
#include
輸出:
priority is 3
9.用fork創(chuàng)建進程
調用fork一次返回2次,分別在父進程和子進程中返回,父進程中其返回值是子進程的進程標識符,子進程中其返回值是0。
#include
(注意保存為UTF-8格式,因為有中文)
輸出:
10.vfork和fork之間的區(qū)別
vfork用于創(chuàng)建一個新進程,而該新進程的目的是exec一個新進程,vfork和fork一樣都創(chuàng)建一個子進程,但是它并不將父進程的地址空間完全復制到子進程中,不會復制頁表。因為子進程會立即調用exec,于是也就不會存放該地址空間。不過在子進程中調用exec或exit之前,他在父進程的空間中運行。
為什么會有vfork,因為以前的fork當它創(chuàng)建一個子進程時,將會創(chuàng)建一個新的地址空間,并且拷貝父進程的資源,而往往在子進程中會執(zhí)行exec調用,這樣,前面的拷貝工作就是白費力氣了,這種情況下,聰明的人就想出了vfork,它產生的子進程剛開始暫時與父進程共享地址空間(其實就是線程的概念了),因為這時候子進程在父進程的地址空間中運行,所以子進程不能進行寫操作,并且在兒子“霸占”著老子的房子時候,要委屈老子一下了,讓他在外面歇著(阻塞),一旦兒子執(zhí)行了exec或者exit后,相當于兒子買了自己的房子了,這時候就相當于分家了。
vfork和fork之間的另一個區(qū)別是: vfork保證子進程先運行,在她調用exec或exit之后父進程才可能被調度運行。如果在調用這兩個函數(shù)之前子進程依賴于父進程的進一步動作,則會導致死鎖。
由此可見,這個系統(tǒng)調用是用來啟動一個新的應用程序。其次,子進程在vfork()返回后直接運行在父進程的棧空間,并使用父進程的內存和數(shù)據(jù)。這意味著子進程可能破壞父進程的數(shù)據(jù)結構或棧,造成失敗。
為了避免這些問題,需要確保一旦調用vfork(),子進程就不從當前的??蚣苤蟹祷?,并且如果子進程改變了父進程的數(shù)據(jù)結構就不能調用exit函數(shù)。子進程還必須避免改變全局數(shù)據(jù)結構或全局變量中的任何信息,因為這些改變都有可能使父進程不能繼續(xù)。
通常,如果應用程序不是在fork()之后立即調用exec(),就有必要在fork()被替換成vfork()之前做仔細的檢查。
用fork函數(shù)創(chuàng)建子進程后,子進程往往要調用一種exec函數(shù)以執(zhí)行另一個程序,當進程調用一種exec函數(shù)時,該進程完全由新程序代換,而新程序則從其main函數(shù)開始執(zhí)行,因為調用exec并不創(chuàng)建新進程,所以前后的進程id 并未改變,exec只是用另一個新程序替換了當前進程的正文,數(shù)據(jù),堆和棧段。
11.exec
清除父進程的可執(zhí)行代碼影像,用新代碼覆蓋父進程。
參考:Linux exec與重定向
#include
12.system創(chuàng)建進程
system系統(tǒng)調用是為了方便調用外部程序,執(zhí)行完畢后返回調用進程。
#include
輸出:
13.退出進程
調用exit退出進程
調用wait等待進程退出
#include
輸出:
二.信號
信號又稱軟終端,通知程序發(fā)生異步事件,程序執(zhí)行中隨時被各種信號中斷,進程可以忽略該信號,也可以中斷當前程序轉而去處理信號,引起信號原因:
1).程序中執(zhí)行錯誤碼;
2).其他進程發(fā)送來的;
3).用戶通過控制終端發(fā)送來;
4).子進程結束時向父進程發(fā)送SIGCLD;
5).定時器生產的SIGALRM;
1.信號分類
#kill -l
獲取信號列表,信號值) ?信號名
1-31是不可靠信號(可能丟失);32-64是可靠信號(操作系統(tǒng)保證不丟失)
信號列表參考:http://blog.csdn.net/21aspnet/article/details/7494565
信號安裝:定義進程收到信號后的處理方法
signal系統(tǒng)調用安裝信號
#include
輸出:
按Ctrl+C
receive signal 2
sigaction系統(tǒng)調用(更多的控制,完全可以替代signal)
#include
輸出:
按Ctrl+C
receive signal 2,addtional data is 12364176
2.信號處理方式3種:
1.忽略信號-大多可以忽略,只有SIGKILL和SIGSTOP除外;
2.捕捉信號-先安裝
3.默認操作
3.信號阻塞
阻塞是指系統(tǒng)內核暫停向進程發(fā)送指定信號,由內核對進程接收到的信號緩存,直到解除阻塞為止。
信號3種進入阻塞的情況:
1.信號處理函數(shù)執(zhí)行過程中,該信號將阻塞;
2.通過sigaction信號安裝,如果設置了sa_mask阻塞信號集;
3.通過系統(tǒng)調用sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
參數(shù):
how:用于指定信號修改的方式,可能選擇有三種
SIG_BLOCK //加入信號到進程屏蔽。
SIG_UNBLOCK //從進程屏蔽里將信號刪除。
SIG_SETMASK //將set的值設定為新的進程屏蔽。
set:為指向信號集的指針,在此專指新設的信號集,如果僅想讀取現(xiàn)在的屏蔽值,可將其置為NULL。
oldset:也是指向信號集的指針,在此存放原來的信號集。
#include
輸出:如果不輸入Ctrl+C則10秒后程序結束;如果期間有Ctrl+C則會10秒結束,之后輸出Creceive signal 2
注意:子進程會繼承父進程的信號掩碼
4.信號集操作
對信號集中所有信號處理
數(shù)據(jù)類型 sigset_t
清空信號集sigemptyset
信號集填充全部信號sigfillset
信號集增加信號sigaddset
信號集中刪除信號sigdelset
判斷信號集是否包含某信號的sigismember
5.未決信號
信號產生后到信號被接收進程處理前的過渡狀態(tài),未決狀態(tài)時間很短。
sigprocmask阻塞某種信號,則向進程發(fā)送這種信號處于未決狀態(tài)。
sigpending獲取當前進程中處于未決狀態(tài)的信號
#include
6.等待信號
阻塞式系統(tǒng)如果沒有符合條件的數(shù)據(jù)將休眠,直到數(shù)據(jù)到來,例如socket上讀取數(shù)據(jù)。有2種狀態(tài)可以中斷該操作
1.網絡上有數(shù)據(jù),讀操作獲取數(shù)據(jù)后返回
2.當前進程接收信號,讀操作被中斷返回失敗,錯誤碼errno為EINTR
pause系統(tǒng)調用可以讓程序暫停執(zhí)行進入休眠,等待信號到來。
#include
7.信號發(fā)送
兩種方式
kill 不可附加數(shù)據(jù)
sigqueue?可附加數(shù)據(jù)
#include
輸出:receive addtional data is 123
8.sigalarm信號
阻塞式系統(tǒng)調用,為避免無限期等待,可以設置定時器信號,alarm調用
#include
輸出:
SIGALRM received.
Pause time out.
9.sigcld信號
父進程捕獲子進程的退出信號
子進程發(fā)送SIGCLD信號進入僵尸狀態(tài);父進程接收到該信號處理,子進程結束
#include
輸出:
三.管道
單向,一段輸入,另一端輸出,先進先出FIFO。管道也是文件。管道大小4096字節(jié)。
特點:管道滿時,寫阻塞;空時,讀阻塞。
分類:普通管道(僅父子進程間通信)位于內存,命名管道位于文件系統(tǒng),沒有親緣關系管道只要知道管道名也可以通訊。
1.pipe建立管道
#include
執(zhí)行
#./a.out ? www
輸出
#www
2.dup
#include
輸出:129
?
Linux execlp函數(shù)
說明:相當于執(zhí)行# ls -l |wc -l 統(tǒng)計當前目錄下文件數(shù)量;ls -l 列出當前文件詳細信息;wc -l
wc參考http://blog.csdn.net/21aspnet/article/details/7515442
linux命令集錦http://blog.csdn.net/21aspnet/article/details/1534099
linux常用命令http://linux.chinaitlab.com/special/linuxcom/
3.popen() 函數(shù)
用于創(chuàng)建一個管道,其內部實現(xiàn)為調用 fork 產生一個子進程,執(zhí)行一個 shell 以運行命令來開啟一個進程,這個進程必須由 pclose() 函數(shù)關閉。
#include
4.命名管道
mknod
mknod 管道名稱 p
#include
mkfifo
mkfifo -m 權限?管道名稱
#include
mknod和mkfifo的區(qū)別
mknod系統(tǒng)調用會產生由參數(shù)path鎖指定的文件,生成文件類型和訪問權限由參數(shù)mode決定。
在很多unix的版本中有一個C庫函數(shù)mkfifo,與mknod不同的是多數(shù)情況下mkfifo不要求用戶有超級用戶的權限
利用命令創(chuàng)建命名管道p1.
#mkfifo -m 0644 p1
#mknod p2 p
#ll
#include
5.管道讀寫
通過open打開,默認是阻塞方式打開,如果open指定O_NONBLOCK則以非阻塞打開。
O_NONBLOCK和O_NDELAY所產生的結果都是使I/O變成非擱置模式(non-blocking),在讀取不到數(shù)據(jù)或是寫入緩沖區(qū)已滿會馬上return,而不會擱置程序動作,直到有數(shù)據(jù)或寫入完成。
它們的差別在于設立O_NDELAY會使I/O函式馬上回傳0,但是又衍生出一個問題,因為讀取到檔案結尾時所回傳的也是0,這樣無法得知是哪中情況;因此,O_NONBLOCK就產生出來,它在讀取不到數(shù)據(jù)時會回傳-1,并且設置errno為EAGAIN。
不過需要注意的是,在GNU C中O_NDELAY只是為了與BSD的程序兼容,實際上是使用O_NONBLOCK作為宏定義,而且O_NONBLOCK除了在ioctl中使用,還可以在open時設定。
#include
? // if((fd = open("p1",O_WRONLY,0)) < 0)//只寫打開管道
? ? {
? ? ? ? perror("open");
? ? ? ? exit(-1);
? ? }
? ? printf("open fifo p1 for write success!n");
? ? close(fd);
}
四.IPC對象
查看ipc對象信息
#ipcs
查看全部ipc對象信息
#ipcs -a
查看消息隊列信息
#ipcs -q
查看共享內存信息
#ipcs -m
查看信號量信息
#ipcs -s
刪除IPC對象的ipcrm
ipcrm -[smq] ID 或者ipcrm -[SMQ] Key
-q ?-Q刪除消息隊列信息 ?例如ipcrm -q 98307
-m -M刪除共享內存信息
-s -S刪除信號量信息
ftok函數(shù)
產生一個唯一的關鍵字值
ftok原型如下:
key_t ftok( char * fname, int id )
fname就是你指定的文件名(該文件必須是存在而且可以訪問的),id是子序號,雖然為int,但是只有8個比特被使用(0-255)。
當成功執(zhí)行的時候,一個key_t值將會被返回,否則 -1 被返回。
? ?在一般的UNIX實現(xiàn)中,是將文件的索引節(jié)點號取出,前面加上子序號得到key_t的返回值。如指定文件的索引節(jié)點號為65538,換算成16進制為 0x010002,而你指定的ID值為38,換算成16進制為0x26,則最后的key_t返回值為0x26010002。
查詢文件索引節(jié)點號的方法是: ls -i
以下為測試程序:
ftok.c
#include
#./a.out
五.消息隊列
消息隊列是先進先出FIFO原則
1.消息結構模板
strut msgbuf
{
long int ?mtype;//消息類型
char mtext[1];//消息內容
}
2.msgget創(chuàng)建消息
#include
#include
3.msgsnd消息發(fā)送
int msgsnd(int msqid, const void *ptr, size_t length, int flag);
此函數(shù)發(fā)送消息到指定的消息對列
#include
隊列中已經有一條消息,長度6字節(jié)
4.msgrcv消息發(fā)送
#include
輸出:
msgrcv return length=[6] text=[123456]
5.msgctl控制消息
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
消息隊列控制函數(shù)
其中msqid為消息隊列描述符
cmd有以下三種:
IPC_RMID:刪除msgid指定的消息隊列,當前在該隊列上的任何消息都被丟棄,對于該命令,buf參數(shù)可忽略
IPC_SET:設置消息隊列msgid_ds結構體的四個成員:msg_perm.uid,msg_perm_gid,msg_perm.mode和msg_qbytes。它們的值來自由buf指向的結構體中的相應成員。
IPC_STAT:給調用者通過buf返回指定消息隊列當前對應msgid_ds結構體
函數(shù)執(zhí)行成功返回0,失敗返回-1
#include
說明: (~0222)取反后做與實際上就是去除其他用戶的寫權限,在C語言中,八進制常用用前綴表示
六.共享內存
共享內存是分配一塊能被其他進程訪問的內存,實現(xiàn)是通過將內存去映射到共享它的進程的地址空間,使這些進程間的數(shù)據(jù)傳送不再涉及內核,即,進程間通信不需要通過進入內核的系統(tǒng)調用來實現(xiàn);
共享內存與其他的進程間通信最大的優(yōu)點是:數(shù)據(jù)的復制只有兩次,一次是從輸入文件到共享內存區(qū),一次從共享內存區(qū)到輸出文件
而其他的則是需要復制4次:服務器將輸入文件讀入自己的進程空間,再從自己的進程空間寫入管道/消息隊列等;客戶進程從管道/消息隊列中讀出數(shù)據(jù)到自己的進程空間,最后輸出到客戶指定的文件中;
要使用共享內存,應該有如下步驟:
1.開辟一塊共享內存 ? ? shmget()
2.允許本進程使用共某塊共享內存 ?shmat()
3.寫入/讀出
4.禁止本進程使用這塊共享內存 ? shmdt()
5.刪除這塊共享內存 ? ? shmctl()或者命令行下ipcrm
1.shmget創(chuàng)建共享內存
#include
int ? ?shmget( key_t shmkey , int shmsiz , int flag );
shmget()是用來開辟/指向一塊共享內存的函數(shù)。參數(shù)定義如下:
key_t shmkey 是這