1. 程式人生 > >Linux程序間通訊--訊號,管道,訊息佇列,訊號量,共享記憶體,socket

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語言中,八進位制常用用字首表示

六.共享記憶體

共享記憶體是分配一塊能被其他程序訪問的記憶體,實現是通過將記憶體去對映到共享它的程序的地址空間,使這些程序間的資料傳送不再涉及核心,即,程序間通訊不需要通過進入核心的系統呼叫來實現;

共享記憶體與其他的