Linux程序間通訊--訊號,管道,訊息佇列,訊號量,共享記憶體,socket
Linux 傳統的程序間通訊有很多,如各類管道、訊息佇列、記憶體共享、訊號量等等。但它們都無法介於核心態與使用者態使用,原因如表
通訊方法 | 無法介於核心態與使用者態的原因 |
管道(不包括命名管道) | 侷限於父子程序間的通訊。 |
訊息佇列 | 在硬、軟中斷中無法無阻塞地接收資料。 |
訊號量 | 無法介於核心態和使用者態使用。 |
記憶體共享 | 需要訊號量輔助,而訊號量又無法使用。 |
套接字 | 在硬、軟中斷中無法無阻塞地接收資料。 |
一.程序
1.程序表
ps顯示正在執行的程序
# ps -ef
TIME 程序目前佔用的cpu時間,CMD顯示啟動程序所使用的命令
#ps ax
STAT表明程序的狀態
S 睡眠,s程序是會話期首程序;R 執行;D 等待;T 停止;Z 殭屍;N 低優先順序任務,nice;W 分頁;
+程序屬於前臺程序組;l 程序是多執行緒;<高優先順序任務
#ps -l 或#ps -al
表現良好的程式為nice程式,系統根據程序的nice值決定他的優先順序
-f是長格式
2.父子程序id
pid當前程序的;
uid當前程序的實際使用者
eid當前程序的有效使用者
#include <stdio.h>
#include <unistd.h>
main()
{
printf("process id=%d\n",getpid());
printf("parent process id=%d\n",getppid());
printf("process group id=%d\n",getpgrp());
printf("calling process's real user id=%d\n",getuid());
printf("calling process's real group id=%d\n",getgid());
printf("calling process's effective user id=%d\n",geteuid());
printf("calling process's effective group id=%d\n",getegid());
}
執行結果:
3.設定程序組id以及程序sleep
setpgid使當前程序為新程序組的組長
#include <stdio.h>
#include <unistd.h>
main()
{
setpgid(0,0); //設定當前程序為新程序組的組長
sleep(8); //休眠8秒
}
說明:setpgid(0,0)等價於setpgrp(0,0)
setpgid(0,0)第1個引數用於指定要修改的程序id。如果為0,則指當前程序。第2個引數用於指定新的程序組id。如果為0,則指當前程序。
先執行程式
#./example13_2
再檢視程序
#ps alef
#ps -ao pid,pgrp,cmd|grep 13_2
或者
#ps -ao pid,pgrp,cmd
4.子程序
fork為0說明是父程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
main()
{
fprintf(stderr,"fork...\n");
if(fork() == 0 )
{
wait();
exit(0);
}
printf("AA");
sleep(10);
exit(0);
}
輸出
fork...
AA
注意:
警告: 隱式宣告與內建函式 ‘exit’ 不相容
警告: 隱式宣告與內建函式 ‘sprintf’ 不相容
警告: 隱式宣告與內建函式 ‘printf’ 不相容
加入這兩個標頭檔案就可以了!
#include <stdio.h>
#include <stdlib.h>
#ps -ao pid,pgrp,cmd
3165就是子程序
#ps alef
5.程序會話
setsid的呼叫程序應該不是某個程序組的組長程序;
setsid呼叫成功後生成新會話,新會話id是呼叫程序的程序id;
新會話只包含一個程序組一個程序即呼叫程序,沒有控制終端。
setid主要是實現程序的後臺執行
#include <stdio.h>
#include <unistd.h>
#include <sys/param.h>
#include <stdlib.h>
main()
{
int n;
__pid_t nPid;
__pid_t nGroupId;
if((nPid = fork()) < 0)
{
perror("fork");
exit(0);
}
if(nPid != 0)//父程序
exit(0);
nGroupId = setsid();//新會話
if(nGroupId == -1)
{
perror("setsid");
exit(0);
}
for(n=0;n<10;n++)
sleep(3);
}
修改後的程式
#include <stdio.h>
#include <unistd.h>
#include <sys/param.h>
#include <stdlib.h>
main()
{
int n;
__pid_t nPid;
__pid_t nGroupId;
if((nPid = fork()) < 0)
{
printf("Error");
exit(0);
}
if(nPid != 0)//父程序
{
perror("aaa");
wait(0);
//exit(0);//父程序退出
}
else
{
perror("bbb");
nGroupId = setsid();//新會話
if(nGroupId == -1)
{
perror("setsid");
exit(0);
}
perror("fff");
sleep(3);
perror("ggg");
exit(0);
}
perror("kkk");
}
父程序必須呼叫wait等待子程序推出,如果沒有子程序退出exit,則wait進入阻塞!
6.程序的控制終端
#tty
在secureCRT中觀看其他的會輸出
/dev/pts/1等依次類推
#ps -ax
檢視程序的控制終端
有列tty的就是控制終端,有值表明程序有控制終端,無則表明是後臺程序。
7.程序的狀態
可執行;
等待;
暫停;
殭屍;
程序在終止前向父程序傳送SIGCLD訊號,父程序呼叫wait等待子程序的退出!
如果,父程序沒有呼叫wait而子程序已經退出,那麼父程序成為殭屍程序;
如果,父程序沒有等子程序退出自己已經先退出,那麼子程序成為孤兒程序;
通過top命令看到
8.程序的優先順序
優先順序數值越低,則優先順序越高!
優先順序由優先級別(PR)+程序的謙讓值(NI) 聯合確定。
PR值是由父程序繼承而來,是不可修改的。
Linux提供nice系統呼叫修改自身的NI值;setpriority系統呼叫可以修改其他程序以及程序組的NI值。
#include <unistd.h>
#include <errno.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
int nPr;
if(nice(3) == -1)//程序的謙讓值是3,優先順序降低
{
perror("nice");
exit(0);
}
errno = 0;
nPr = getpriority(PRIO_PROCESS,getpid());//獲取當前程序的謙讓值
if(errno != 0)
{
perror("getpriority");
exit(0);
}
printf("priority is %d\n",nPr);
}
輸出:
priority is 3
9.用fork建立程序
呼叫fork一次返回2次,分別在父程序和子程序中返回,父程序中其返回值是子程序的程序識別符號,子程序中其返回值是0。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
signal(SIGCLD, SIG_IGN);//訊號處理,忽略SIGCLD訊號,避免形成殭屍程序
switch(pid=fork())//建立子程序
{
case -1:
perror("fork");
break;
case 0://子程序
printf("子程序:程序ID=%d\n",getpid());
printf("pid=%d\n", pid);
exit(0);
break;
default://父程序
printf("父程序:程序ID=%d\n",getpid());
printf("pid=%d\n", pid);
sleep(5);
break;
}
}
(注意儲存為UTF-8格式,因為有中文)
輸出:
10.vfork和fork之間的區別
vfork用於建立一個新程序,而該新程序的目的是exec一個新程序,vfork和fork一樣都建立一個子程序,但是它並不將父程序的地址空間完全複製到子程序中,不會複製頁表。因為子程序會立即呼叫exec,於是也就不會存放該地址空間。不過在子程序中呼叫exec或exit之前,他在父程序的空間中執行。
為什麼會有vfork,因為以前的fork當它建立一個子程序時,將會建立一個新的地址空間,並且拷貝父程序的資源,而往往在子程序中會執行exec呼叫,這樣,前面的拷貝工作就是白費力氣了,這種情況下,聰明的人就想出了vfork,它產生的子程序剛開始暫時與父程序共享地址空間(其實就是執行緒的概念了),因為這時候子程序在父程序的地址空間中執行,所以子程序不能進行寫操作,並且在兒子“霸佔”著老子的房子時候,要委屈老子一下了,讓他在外面歇著(阻塞),一旦兒子執行了exec或者exit後,相當於兒子買了自己的房子了,這時候就相當於分家了。
vfork和fork之間的另一個區別是: vfork保證子程序先執行,在她呼叫exec或exit之後父程序才可能被排程執行。如果在呼叫這兩個函式之前子程序依賴於父程序的進一步動作,則會導致死鎖。
由此可見,這個系統呼叫是用來啟動一個新的應用程式。其次,子程序在vfork()返回後直接執行在父程序的棧空間,並使用父程序的記憶體和資料。這意味著子程序可能破壞父程序的資料結構或棧,造成失敗。
為了避免這些問題,需要確保一旦呼叫vfork(),子程序就不從當前的棧框架中返回,並且如果子程序改變了父程序的資料結構就不能呼叫exit函式。子程序還必須避免改變全域性資料結構或全域性變數中的任何資訊,因為這些改變都有可能使父程序不能繼續。
通常,如果應用程式不是在fork()之後立即呼叫exec(),就有必要在fork()被替換成vfork()之前做仔細的檢查。
用fork函式建立子程序後,子程序往往要呼叫一種exec函式以執行另一個程式,當程序呼叫一種exec函式時,該程序完全由新程式代換,而新程式則從其main函式開始執行,因為呼叫exec並不建立新程序,所以前後的程序id 並未改變,exec只是用另一個新程式替換了當前程序的正文,資料,堆和棧段。
11.exec
清除父程序的可執行程式碼影像,用新程式碼覆蓋父程序。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
main()
{
pid_t pid;
char *para[]={"ls","-a",NULL};
if((pid = fork()) < 0)
{
perror("fork");
exit(0);
}
if(pid == 0)
{
if(execl("/bin/ls","ls","-l",(char *)0) == -1)
{
perror("execl");
exit(0);
}
}
if((pid = fork()) < 0)
{
perror("fork");
exit(0);
}
if(pid == 0)
{
if(execv("/bin/ls",para) == -1)
{
perror("execl");
exit(0);
}
}
return;
}
12.system建立程序
system系統呼叫是為了方便呼叫外部程式,執行完畢後返回呼叫程序。
#include <stdio.h>
#include <stdlib.h>
main()
{
printf("call ls return %d\n",system("ls -l"));
}
輸出:
13.退出程序
呼叫exit退出程序
呼叫wait等待程序退出
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void handle_sigcld(int signo)
{
pid_t pid;
int status;
if((pid = wait(&status)) != -1)
{
printf("子程序%d退出\n",pid);
}
if(WIFEXITED(status))
{
printf("子程序返回%d\n",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))
{
printf("子程序被訊號ť%d結束\n",WTERMSIG(status));
}
}
main()
{
pid_t pid;
signal(SIGCLD,handle_sigcld);
if((pid = fork()) < 0)
{
perror("fork");
exit(0);
}
if(pid == 0)
{
exit(123);
}
sleep(5);
}
輸出:
二.訊號
訊號又稱軟終端,通知程式發生非同步事件,程式執行中隨時被各種訊號中斷,程序可以忽略該訊號,也可以中斷當前程式轉而去處理訊號,引起訊號原因:
1).程式中執行錯誤碼;
2).其他程序傳送來的;
3).使用者通過控制終端傳送來;
4).子程序結束時向父程序傳送SIGCLD;
5).定時器生產的SIGALRM;
1.訊號分類
#kill -l
獲取訊號列表,訊號值) 訊號名
1-31是不可靠訊號(可能丟失);32-64是可靠訊號(作業系統保證不丟失)
訊號安裝:定義程序收到訊號後的處理方法
signal系統呼叫安裝訊號
#include <stdio.h>
#include <signal.h>
void HandleSigint(int signo)//訊號處理函式
{
printf("receive signal %d\n",signo);
}
main()
{
if(signal(SIGINT,HandleSigint) == SIG_ERR)//安裝訊號
{
perror("signal");
exit(0);
}
pause();//暫停程序等待訊號
}
輸出:
按Ctrl+C
receive signal 2
sigaction系統呼叫(更多的控制,完全可以替代signal)
#include <stdio.h>
#include <signal.h>
void HandleSigint(int signo,siginfo_t *info,void *none)
{
printf("receive signal %d,addtional data is %d\n",signo,info->si_value.sival_int);
}
main()
{
struct sigaction act,oact;//訊號處理函式結構
memset(&act,0x00,sizeof(struct sigaction));//清空結構
sigemptyset(&act.sa_mask);//清空訊號處理掩碼
act.sa_sigaction = HandleSigint;//定義訊號處理函式
act.sa_flags = SA_SIGINFO;//指定傳送訊號時可以附加資料
if(sigaction(SIGINT,&act,&oact) == -1)//安裝
{
perror("sigaction");
exit(0);
}
pause();//暫停
}
輸出:
按Ctrl+C
receive signal 2,addtional data is 12364176
2.訊號處理方式3種:
1.忽略訊號-大多可以忽略,只有SIGKILL和SIGSTOP除外;
2.捕捉訊號-先安裝
3.預設操作
3.訊號阻塞
阻塞是指系統核心暫停向程序傳送指定訊號,由核心對程序接收到的訊號快取,直到解除阻塞為止。
訊號3種進入阻塞的情況:
1.訊號處理函式執行過程中,該訊號將阻塞;
2.通過sigaction訊號安裝,如果設定了sa_mask阻塞訊號集;
3.通過系統呼叫sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
引數:
how:用於指定訊號修改的方式,可能選擇有三種
SIG_BLOCK //加入訊號到程序遮蔽。
SIG_UNBLOCK //從程序遮蔽裡將訊號刪除。
SIG_SETMASK //將set的值設定為新的程序遮蔽。
set:為指向訊號集的指標,在此專指新設的訊號集,如果僅想讀取現在的遮蔽值,可將其置為NULL。
oldset:也是指向訊號集的指標,在此存放原來的訊號集。
#include <stdio.h>
#include <signal.h>
void handle_sigint(int signo)
{
printf("receive signal %d\n",signo);
}
main()
{
sigset_t mask;
sigset_t omask;
signal(SIGINT,handle_sigint);
sigemptyset(&mask);//清空訊號處理掩碼
sigaddset(&mask,SIGINT);//向掩碼中增加訊號
sigprocmask(SIG_BLOCK,&mask,&omask);//設定掩碼,設定完成後SIGINT訊號被阻塞
sleep(10);
sigprocmask(SIG_SETMASK,&omask,NULL);//恢復原有的訊號處理掩碼
}
輸出:如果不輸入Ctrl+C則10秒後程序結束;如果期間有Ctrl+C則會10秒結束,之後輸出Creceive signal 2
注意:子程序會繼承父程序的訊號掩碼
4.訊號集操作
對訊號集中所有訊號處理
資料型別 sigset_t
清空訊號集sigemptyset
訊號集填充全部訊號sigfillset
訊號集增加訊號sigaddset
訊號集中刪除訊號sigdelset
判斷訊號集是否包含某訊號的sigismember
5.未決訊號
訊號產生後到訊號被接收程序處理前的過渡狀態,未決狀態時間很短。
sigprocmask阻塞某種訊號,則向程序傳送這種訊號處於未決狀態。
sigpending獲取當前程序中處於未決狀態的訊號
#include <stdio.h>
#include <signal.h>
void handle_sigint(int signo)
{
printf("receive signal %d\n",signo);
}
main()
{
sigset_t mask;
sigset_t omask;
sigset_t pmask;
signal(SIGINT,handle_sigint);
sigemptyset(&mask);
sigaddset(&mask,SIGINT);
sigprocmask(SIG_BLOCK,&mask,&omask);
sleep(10);
if(sigpending(&pmask) < 0)//獲取當前未決的訊號集
{
perror("sigpending");
exit(0);
}
if(sigismember(&pmask,SIGINT))//判定SIGINT是否在未決訊號集中
{
printf("SIGINT signal is pending.\n");
}
}
6.等待訊號
阻塞式系統如果沒有符合條件的資料將休眠,直到資料到來,例如socket上讀取資料。有2種狀態可以中斷該操作
1.網路上有資料,讀操作獲取資料後返回
2.當前程序接收訊號,讀操作被中斷返回失敗,錯誤碼errno為EINTR
pause系統呼叫可以讓程式暫停執行進入休眠,等待訊號到來。
#include <stdio.h>
#include <signal.h>
int nInterrupt;
void handle_sigint(int signo)
{
nInterrupt = 1;
}
main()
{
sigset_t mask,omask;
signal(SIGINT,handle_sigint);
sigemptyset(&mask);
sigaddset(&mask,SIGINT);
sigprocmask(SIG_BLOCK,&mask,&omask);//阻塞
nInterrupt = 0;
while(!nInterrupt)//迴圈呼叫sigsuspend等待訊號,直到收到SIGINT,nInterrupt為1
sigsuspend(&omask);//阻塞訊號直到有訊號到達
printf("process return.\n");
}
7.訊號傳送
兩種方式
kill 不可附加資料
sigqueue 可附加資料
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void HandleSigint(int signo,siginfo_t *info,void *none)
{
printf("receive addtional data is %d\n",info->si_value.sival_int);
exit(0);
}
main()
{
int pid;
struct sigaction act;
union sigval sigvalPara;
if((pid = fork()) == 0)
{
memset(&act,0x00,sizeof(struct sigaction));
sigemptyset(&act.sa_mask);
act.sa_sigaction = HandleSigint;
act.sa_flags = SA_SIGINFO;
if(sigaction(SIGINT,&act,NULL) == -1)
{
perror("sigaction");
exit(0);
}
pause();//暫停子程序,等待訊號
}
else
{
sigvalPara.sival_int = 123;//設定附加資料為123
if(sigqueue(pid,SIGINT,sigvalPara) == -1)//向子程序傳送訊號SIGINT,並附加資料
{
perror("sigqueue");
exit(0);
}
}
}
輸出:receive addtional data is 123
8.sigalarm訊號
阻塞式系統呼叫,為避免無限期等待,可以設定定時器訊號,alarm呼叫
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int timeout;
void Handle_Alarm(int signo)
{
timeout = 1;
printf("SIGALRM received.\n");
}
main()
{
if(signal(SIGALRM,Handle_Alarm) ==SIG_ERR )//安裝SIGALRM訊號
{
perror("signal");
exit(0);
}
timeout = 0;//設定超時標誌為0
alarm(10);//啟動定時器
pause();//阻塞程序,等待訊號
if(timeout)//如果超時
{
printf("Pause time out.\n");
}
}
輸出:
SIGALRM received.
Pause time out.
9.sigcld訊號
父程序捕獲子程序的退出訊號
子程序傳送SIGCLD訊號進入殭屍狀態;父程序接收到該訊號處理,子程序結束
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void Handle_Sigcld(int signo)
{
int pid,status;
pid = waitpid(-1,&status,0);
printf("Child process %d exit with code %d\n",pid,status);
}
main()
{
int i,pid;
signal(SIGCLD,Handle_Sigcld);
for(i=0;i<5;i++)
{
if((pid = fork()) == 0)//子程序
{
srand(getpid());//產生隨機數
exit((int)(rand()/1024));//退出子程序,退出碼為上步隨機數
}
else
{//父程序
sleep(1);//休眠
continue;//繼續
}
}
}
輸出:
三.管道
單向,一段輸入,另一端輸出,先進先出FIFO。管道也是檔案。管道大小4096位元組。
特點:管道滿時,寫阻塞;空時,讀阻塞。
分類:普通管道(僅父子程序間通訊)位於記憶體,命名管道位於檔案系統,沒有親緣關係管道只要知道管道名也可以通訊。
1.pipe建立管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int pfd[2];//儲存開啟管道後的兩個檔案描述符
pid_t cpid;//儲存程序識別符號
char buf;
if(argc != 2)//判斷命令列引數是否符合
{
fprintf(stderr,"Usage: %s <string>\n",argv[0]);
exit(0);
}
if (pipe(pfd) == -1)//建立管道
{
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0)//子程序
{
close(pfd[1]); //關閉管道寫,引用計數-1
while (read(pfd[0], &buf, 1) > 0)//從管道迴圈讀取資料
write(STDOUT_FILENO, &buf, 1);//輸出讀到的資料
write(STDOUT_FILENO, "\n", 1);//輸出從管道讀取的資料
close(pfd[0]);//關閉管道讀,引用計數-1
exit(0);
}
else
{//父程序
close(pfd[0]);
write(pfd[1], argv[1], strlen(argv[1]));//向管道寫入命令列引數1
close(pfd[1]);
wait(NULL); //等待子程序退出
exit(0);
}
}
說明:每呼叫一次fork 都要關閉一次程序描述符
執行
#./a.out www
輸出
#www
2.dup
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
int pfds[2];
if ( pipe(pfds) == 0 )
{
if ( fork() == 0 )//子程序
{
close(1);//關閉標準輸出
dup2( pfds[1], 1 );//管道的寫檔案描述符複製到程序的輸出
close( pfds[0] );//關閉管道讀
execlp( "ls", "ls","-l", NULL );//執行ls -l 輸出寫入管道
}
else
{
close(0);
dup2( pfds[0], 0 );//管道的讀檔案描述符複製到程序的輸入
close( pfds[1] );
execlp( "wc", "wc", "-l", NULL );//執行wc -l 將管道讀取資料作為wc命令的輸入
}
}
return 0;
}
輸出:129
說明:相當於執行# ls -l |wc -l 統計當前目錄下檔案數量;ls -l 列出當前檔案詳細資訊;wc -l
3.popen() 函式
用於建立一個管道,其內部實現為呼叫 fork 產生一個子程序,執行一個 shell 以執行命令來開啟一個程序,這個程序必須由 pclose() 函式關閉。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main( void )
{
FILE *stream;//檔案流
char buf[1024];//讀寫緩衝區
memset( buf, '\0', sizeof(buf) );//清空
stream = popen( "wc -l", "w" );
for(;;)
{
memset(buf,0x00,sizeof(buf));
scanf("%s",buf);//接受輸入
if(strcmp(buf,"k") == 0)//如果是k就退出
{
break;
}
fprintf(stream,"%s\n",buf);//寫入
}
pclose( stream );//關閉
return 0;
}
輸出:
4.命名管道
mknod
mknod 管道名稱 p
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
mkfifo
mkfifo -m 許可權 管道名稱
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char * pathname,mode_t mode);
mknod和mkfifo的區別
mknod系統呼叫會產生由引數path鎖指定的檔案,生成檔案型別和訪問許可權由引數mode決定。
在很多unix的版本中有一個C庫函式mkfifo,與mknod不同的是多數情況下mkfifo不要求使用者有超級使用者的許可權
利用命令建立命名管道p1.
#mkfifo -m 0644 p1
#mknod p2 p
#ll
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
main()
{
if(mkfifo("p1",0644) < 0)
{
perror("mkfifo");
exit(-1);
}
return;
}
5.管道讀寫
通過open開啟,預設是阻塞方式開啟,如果open指定O_NONBLOCK則以非阻塞開啟。
O_NONBLOCK和O_NDELAY所產生的結果都是使I/O變成非擱置模式(non-blocking),在讀取不到資料或是寫入緩衝區已滿會馬上return,而不會擱置程式動作,直到有資料或寫入完成。
它們的差別在於設立O_NDELAY會使I/O函式馬上回傳0,但是又衍生出一個問題,因為讀取到檔案結尾時所回傳的也是0,這樣無法得知是哪中情況;因此,O_NONBLOCK就產生出來,它在讀取不到資料時會回傳-1,並且設定errno為EAGAIN。
不過需要注意的是,在GNU C中O_NDELAY只是為了與BSD的程式相容,實際上是使用O_NONBLOCK作為巨集定義,而且O_NONBLOCK除了在ioctl中使用,還可以在open時設定。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
main()
{
int fd;
if((fd = open("p1",O_RRONLY,0)) < 0)//只讀開啟管道
// 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函式
產生一個唯一的關鍵字值
ftok原型如下:
key_t ftok( char * fname, int id )
fname就是你指定的檔名(該檔案必須是存在而且可以訪問的),id是子序號,雖然為int,但是隻有8個位元被使用(0-255)。
當成功執行的時候,一個key_t值將會被返回,否則 -1 被返回。
在一般的UNIX實現中,是將檔案的索引節點號取出,前面加上子序號得到key_t的返回值。如指定檔案的索引節點號為65538,換算成16進製為 0x010002,而你指定的ID值為38,換算成16進製為0x26,則最後的key_t返回值為0x26010002。
查詢檔案索引節點號的方法是: ls -i
以下為測試程式:
ftok.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define IPCKEY 0x11
int main( void )
{
int i=0;
for ( i = 1; i < 256; ++ i )
printf( "key = %x\n", ftok( "/tmp", i ) );
return 0;
}
#ls -i ftok.c
#./a.out
五.訊息佇列
訊息佇列是先進先出FIFO原則
1.訊息結構模板
strut msgbuf
{
long int mtype;//訊息型別
char mtext[1];//訊息內容
}
2.msgget建立訊息
#include <sys/msg.h>
int msgget(key_t key, int flag);
此函式返回key指定訊息的識別符號
key 一般有ftok函式產生 ,該函式為key_t ftok(const char *pathname, int proj_id);
該函式把從pathname匯出的資訊與id低8位組合成一個整數IPC鍵, 呼叫時pathname必須存在,若不存在ftok呼叫失敗,返回-1,成功返回該整數IPC鍵值
flag 為該訊息佇列的讀寫許可權組合,可以與IPC_CREAT 或IPC_EXCL相與,其中建立對列時都要使用IPC_CREAT,其中IPC_CREAT|IPC_EXCL含義是若已有該佇列則返回錯誤
此函式成功時,返回非負佇列識別符號;失敗時返回-1
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
main()
{
key_t lKey;
int nMsgId;
if((lKey = ftok("/etc/profile",1)) == -1)
{
perror("ftok");
exit(1);
}
//帶引數IPC_CREAT和IPC_EXCL,如果佇列不存在則建立佇列,已存在則返回EEXIST
if((nMsgId = msgget(lKey,IPC_CREAT|IPC_EXCL|0666)) == -1)
{
if(errno != EEXIST)//建立失敗且不是由於佇列已存在
{
perror("msgget");
exit(2);
}
if((nMsgId = msgget(lKey,0)) == -1)//已存在
{
perror("msgget");
exit(3);
}
}
printf("MsgID=%d\n",nMsgId);
return 0;
}
3.msgsnd訊息傳送
int msgsnd(int msqid, const void *ptr, size_t length, int flag);
此函式傳送訊息到指定的訊息對列
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct
{
long int nType;
char szText[256];
}MSG;
main()
{
key_t lKey;
int nMsgId;
MSG msg;
if((lKey = ftok("/etc/profile",1)) == -1)//生成鍵值
{
perror("ftok");
exit(1);
}
if((nMsgId = msgget(lKey,IPC_CREAT|IPC_EXCL|0666)) == -1)//建立訊息佇列
{
if(errno != EEXIST)
{
perror("msgget");
exit(2);
}
if((nMsgId = msgget(lKey,0)) == -1)
{
perror("msgget");
exit(3);
}
}
memset(&msg,0x00,sizeof(MSG));//清空佇列
msg.nType = 2;//指定訊息型別為2
memcpy(msg.szText,"123456",6);//指定訊息內容
if(msgsnd(nMsgId,(const void *)&msg,strlen(msg.szText),IPC_NOWAIT) < 0)//非阻塞傳送訊息
{
perror("msgsnd");
}
return 0;
}
佇列中已經有一條訊息,長度6位元組
4.msgrcv訊息傳送
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct
{
long int nType;
char szText[256];
}MSG;
main()
{
key_t lKey;
int n,nMsgId;
MSG msg;
if((lKey = ftok("/etc/profile",1)) == -1)
{
perror("ftok");
exit(1);
}
if((nMsgId = msgget(lKey,0)) == -1)
{
perror("ftok");
exit(2);
}
memset(&msg,0x00,sizeof(MSG));
if((n = msgrcv(nMsgId,(void *)&msg,sizeof(msg.szText),2L,0)) < 0)//從佇列接收訊息,讀出以後就不存在了
{
perror("msgrcv");
}
else
{
printf("msgrcv return length=[%d] text=[%s]\n",n,msg.szText);//輸出
}
return 0;
}
輸出:
msgrcv return length=[6] text=[123456]5.msgctl控制訊息
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
訊息佇列控制函式
其中msqid為訊息佇列描述符
cmd有以下三種:
IPC_RMID:刪除msgid指定的訊息佇列,當前在該佇列上的任何訊息都被丟棄,對於該命令,buf引數可忽略
IPC_SET:設定訊息佇列msgid_ds結構體的四個成員:msg_perm.uid,msg_perm_gid,msg_perm.mode和msg_qbytes。它們的值來自由buf指向的結構體中的相應成員。
IPC_STAT:給呼叫者通過buf返回指定訊息隊列當前對應msgid_ds結構體
函式執行成功返回0,失敗返回-1
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct
{
long int nType;
char szText[256];
}MSG;
main()
{
key_t lKey;
int n,nMsgId;
MSG msg;
struct msqid_ds qds;
if((lKey = ftok("/etc/profile",1)) == -1)
{
perror("ftok");
exit(1);
}
if((nMsgId = msgget(lKey,0)) == -1)
{
perror("ftok");
exit(2);
}
memset(&qds,0x00,sizeof(struct msqid_ds));
if(msgctl(nMsgId,IPC_STAT,&qds) < 0)//獲取訊息佇列屬性,獲取狀態放pds中
{
perror("msgctl IPC_STAT");
exit(3);
}
printf("msg_perm.mode=%d\n",qds.msg_perm.mode);
qds.msg_perm.mode &= (~0222);//去除訊息佇列的寫許可權
if(msgctl(nMsgId,IPC_SET,&qds) < 0)//設定訊息佇列許可權
{
perror("msgctl IPC_SET");
exit(4);
}
memset(&msg,0x00,sizeof(MSG));
msg.nType = 2;
memcpy(msg.szText,"12345",5);
if(msgsnd(nMsgId,(void *)&msg,5,0) < 0)//傳送訊息
{
perror("msgsnd");
}
if(msgctl(nMsgId,IPC_RMID,NULL) < 0)//刪除訊息
{
perror("msgctl IPC_RMID");
exit(5);
}
return 0;
}
說明: (~0222)取反後做與實際上就是去除其他使用者的寫許可權,在C語言中,八進位制常用用字首表示
六.共享記憶體
共享記憶體是分配一塊能被其他程序訪問的記憶體,實現是通過將記憶體去對映到共享它的程序的地址空間,使這些程序間的資料傳送不再涉及核心,即,程序間通訊不需要通過進入核心的系統呼叫來實現;
共享記憶體與其他的