linux c編程:進程間通訊

進程間的通訊包括管道,共享內存,信號量通訊,消息隊列,套藉口(socket)和全雙工管道通訊linux

首先來看下管道的用法:管道顧名思義,就如同下水道管道同樣,當從管道一端流水到另外一端的時候,水流的方向是單方向的。某一時刻只能從單方向傳遞數據,不能雙向傳遞。這種就叫單雙工模式。半雙工模式只能是一端寫數據,一端讀數據。來看一個半雙工的例子:shell

(1)在父進程中經過pipe()函數建立一個管道。產生一個描述符,fd[0]指向管道的讀端,fd[1]指向管道的寫端ubuntu

(2) 在父進程中調用fork函數產生一個子進程。子進程和父進程都分別指向fd的讀端和寫端數組

(3) 在子進程中,關閉fd[0]也就是讀的端口,往fd[1]寫端口寫入數據,在父進程中關閉寫端口fd[1],從讀端口fd[0]中讀取數據並顯示在終端上。在父進程中調用了sleep(2)等待2秒的函數,目的是等待子進程寫數據數據結構

int pipe_function()
{
    int fd[2],pid,line;
    char message[100];
    if (pipe(fd) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        wait(NULL);
        exit(0);
    }
    return 0;
}socket

運行結果:函數

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3spa

Child process send message操作系統

Parent process receive message is:指針

Welcome to microsof


上面的例子演示了單向通訊,若是咱們想獲得雙向通訊,父進程在讀的同時也給子進程寫。要實現這樣的功能,咱們就必須創建2個管道,一個管道分別是從父進程流向子進程,一個管道是從子進程流向父進程。代碼實現以下:定義了兩個管道fdfd1

int pipe_function_multiy()
{
    int fd[2],pid,line,fd1[2],line1;
    char message[100],message1[100];
    if (pipe(fd) == -1 || pipe(fd1) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
        sleep(2);
        close(fd1[1]);
        printf("Child process receive message is:\n");
        line1=read(fd1[0],message1,100);
        write(STDOUT_FILENO,message1,line1);
        printf("\n");
        exit(0);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        close(fd1[0]);
        printf("parent process send message is:\n");
        write(fd1[1],"hello how are you!",20);
        exit(0);
    }
    return 0;
}

執行結果:

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

Parent process receive message is:

Child process send message

Welcome to microsof

parent process send message is:

Child process receive message is:

hello how are you!

 

 

 

在上面的例子中,管道只能在有關聯的進程中進行,也就是父子進程。那若是不相關的兩個進程也須要進行通訊該如何解決呢。這裏就要到命令管道。一般稱爲FIFO。經過這個名稱能夠知道命令管道遵循先進先出的原則。建立一個命令管道有兩種方法一種是經過函數建立命名管道,一種是經過shell命令來建立。

 

首先來看下經過shell命令建立:

 

()首先經過mkfifo建立一個管道文件test

 

root@zhf-linux:/home/zhf/zhf# mkfifo test

 

()在一個終端中經過cat命令來查看這個命令管道中的數據。因爲沒有任何寫入的數據,所以一直處於等待。這個時候cat命令將一直掛起,直到終端或者有數據發送到FIFO中。

 

root@zhf-linux:/home/zhf/zhf# cat ./test

 

()而後打開另一個終端,向FIFO中寫入數據

 

root@zhf-linux:/home/zhf/zhf# echo "hello fifo" > ./test

 

()這個時候cat命令將會輸出內容

 

root@zhf-linux:/home/zhf/zhf# cat ./test

 

hello fifo

 


 


 

那接下來咱們看下經過C代碼的方式來實現命令管道的方法:

 

首先建立2個文件。表明2個進程,一個讀,一個寫。仍是用剛纔建立的test這個命令管道文件

 

test3.c

int mkfifo_function_a()
{
    int fd;
    int pid;
    fd=open(FIFO,O_RDWR);
    printf("write the message:\n");
    write(fd,"hello world",20);
    close(fd);
    
}

test4.c

int mkfifo_function_b()
{
    char msg[100];
    int fd;
    int pid;
    int line;
    fd=open(FIFO,O_RDWR);
    printf("read the message:\n");
    line=read(fd,msg,100);
    close(fd);
    write(STDOUT_FILENO,msg,line);
}

 

 

flags=O_RDONLYopen將會調用阻塞,除非有另一個進程以寫的方式打開同一個FIFO,不然一直等待。

 

flags=O_WRONLYopen將會調用阻塞,除非有另一個進程以讀的方式打開同一個FIFO,不然一直等待。

 

flags=O_RDONLY|O_NONBLOCK:若是此時沒有其餘進程以寫的方式打開FIFO,此時open也會成功返回,此時FIFO被讀打開,而不會返回錯誤。

 

flags=O_WRONLY|O_NONBLOCK:當即返回,若是此時沒有其餘進程以讀的方式打開,open會失敗打開,此時FIFO沒有被打開,返回-1

 



 

()在一個終端中運行test4.c。顯示讀取數據。因爲沒有輸入數據,所以一直掛起

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test4

 

read the message:

 

() 在另一個終端中運行test3.c。寫入數據

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

 

write the message:

 

()此時顯示出讀取信息:

 

root@zhf-linux:/home/zhf/zhf/c_prj# ./test4

 

read the message:

 

hello world

 

 

共享內存:

 

前面介紹到父子進程分別是訪問不一樣的內存,由於子進程拷貝了另一分內存地址。那麼若是想兩個進程訪問同一塊內存地址的數據,就須要用到共享內存了。先來看幾個共享內存的函數:

 

shmget: 建立一塊共享內存區域。若是已存在這一塊的共享內存。該函數能夠打開這個已經存在的共享內存。原型爲:

 

int shmget(key_t key, size_t size, int shmflg);

 

第一個參數,與信號量的semget函數同樣,程序須要提供一個參數key(非0整數),它有效地爲共享內存段命名,shmget()函數成功時返回一個與key相關的共享內存標識符(非負整數),用於後續的共享內存函數。調用失敗返回-1.

 

不相關的進程能夠經過該函數的返回值訪問同一共享內存,它表明程序可能要使用的某個資源,程序對全部共享內存的訪問都是間接的,程序先經過調用shmget()函數並提供一個鍵,再由系統生成一個相應的共享內存標識符(shmget()函數的返回值),只有shmget()函數才直接使用信號量鍵,全部其餘的信號量函數使用由semget函數返回的信號量標識符。

 

第二個參數,size以字節爲單位指定須要共享的內存容量

 

第三個參數,shmflg是權限標誌,它的做用與open函數的mode參數同樣,若是要想在key標識的共享內存不存在時,建立它的話,能夠與IPC_CREAT作或操做。共享內存的權限標誌與文件的讀寫權限同樣,舉例來講,0644,它表示容許一個進程建立的共享內存被內存建立者所擁有的進程向共享內存讀取和寫入數據,同時其餘用戶建立的進程只能讀取共享內存。

 

shmat函數:

 

shmat()函數的做用就是用來啓動對該共享內存的訪問,並把共享內存鏈接到當前進程的地址空間。它的原型以下:

 

void *shmat(int shm_id, const void *shm_addr, int shmflg);

 

第一個參數,shm_id是由shmget()函數返回的共享內存標識。

 

第二個參數,shm_addr指定共享內存鏈接到當前進程中的地址位置,一般爲空,表示讓系統來選擇共享內存的地址。

 

第三個參數,shm_flg是一組標誌位,一般爲0

 

調用成功時返回一個指向共享內存第一個字節的指針,若是調用失敗返回-1.

 

shmdt函數:

 

該函數用於將共享內存從當前進程中分離。注意,將共享內存分離並非刪除它,只是使該共享內存對當前進程再也不可用。它的原型以下:

 

int shmdt(const void *shmaddr);

 

參數 shmaddrshmat()函數返回的地址指針,調用成功時返回 0,失敗時返回 -1.

 

shmctl函數:用來控制共享內存

 

int shmctl(int shm_id, int command, struct shmid_ds *buf);

 

第一個參數,shm_idshmget()函數返回的共享內存標識符。

 

第二個參數,command是要採起的操做,它能夠取下面的三個值 :

 

  • IPC_STAT:把shmid_ds結構中的數據設置爲共享內存的當前關聯值,即用共享內存的當前關聯值覆蓋shmid_ds的值。

  • IPC_SET:若是進程有足夠的權限,就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值

  • IPC_RMID:刪除共享內存段

 

第三個參數,buf是一個結構指針,它指向共享內存模式和訪問權限的結構。

 

下面來看下一個簡單的父子進程訪問共享內存的方法:
int share_memory_function()
{
    int shmid;
    int proj_id;
    key_t key;
    int sizz;
    char *addr;
    pid_t pid;
    key=IPC_PRIVATE;
    shmid=shmget(key,1024,IPC_CREAT|0660);
    if (shmid == -1)
    {
        perror("create share memory failed!");
        return 1;
    }
    addr=(char *)shmat(shmid,NULL,0);
    if(addr == (char *)(-1))
    {
        perror("can not attach");
        return 1;
    }
    strcpy(addr,"welcome to ubuntun");
    pid=fork();
    if (pid == -1)
    {
        perror("error!");
        return 1;
    }
    if (pid == 0)
    {
        printf("Child process string is %s\n",addr);
        exit(0);
    }
    else
    {
        wait(NULL);
        printf("Parent process string is %s\n",addr);
        if (shmdt(addr) == 1)
        {
            perror("release failed");
            return 1;
        }
        if (shmctl(shmid,IPC_RMID,NULL)==-1)
        {
            perror("failed");
            return 1;
        }
    }
    return 0;

}
運行結果:
root@zhf-linux:/home/zhf/zhf/c_prj# ./test4
Child process string is welcome to ubuntun
Parent process string is welcome to ubuntun
在共享內存中,只要獲取到了共享內存的地址,如何進程均可以操做內存,這樣就會致使一個問題,多個進程對內存中的變量進行讀寫。從而使得變量的值變得不可控。信號量就能夠解決這個問題。當有一個進程要求使用某一個共享內存中的資源時,系統會首先判斷該資源的信號量,若是大於 0,則可使用該資源,而且信號量要減一,當再也不使用該資源的時候,信號量再加一。若是信號量等於 0,就進入了休眠狀態。資源不可訪問。
來看下信號量的使用:

1、建立信號量

semget函數建立一個信號量集或訪問一個已存在的信號量集。

#include <sys/sem.h>

int semget (key_t key, int nsem, int oflag) ;

返回值是一個稱爲信號量標識符的整數,semopsemctl函數將使用它。

參數nsem指定集合中的信號量數。(若用於訪問一個已存在的集合,那就能夠把該參數指定爲0

參數oflag能夠是SEM_R(read)SEM_A(alter)常值的組合。(打開時用到),也能夠是IPC_CREATIPC_EXCL ;

 

2、打開信號量

使用semget打開一個信號量集後,對其中一個或多個信號量的操做就使用semop(op--operate)函數來執行。

#include <sys/sem.h>

int semop (int semid, struct sembuf * opsptr, size_t nops) ;

參數opsptr是一個指針,它指向一個信號量操做數組,信號量操做由sembuf結構表示:

 

struct sembuf{

short sem_num; // 除非使用一組信號量,不然它爲0

short sem_op; // 信號量在一次操做中須要改變的數據,一般是兩個數,

// 一個是-1,即P(等待)操做,一個是+1,即V(發送信號)操做

short sem_flg; // 一般爲SEM_UNDO,使操做系統跟蹤信號,並在進程沒有釋放該信號量而終止時,

// 操做系統釋放信號量

};

◆參數nops規定opsptr數組中元素個數。

sem_op值:

1)若sem_op爲正,這對應於進程釋放佔用的資源數。sem_op值加到信號量的值上。(V操做)

2)若sem_op爲負,這表示要獲取該信號量控制的資源數。信號量值減去sem_op的絕對值。(P操做)

3)若sem_op0,這表示調用進程但願等待到該信號量值變成0

◆若是信號量值小於sem_op的絕對值(資源不能知足要求),則:

1)若指定了IPC_NOWAIT,則semop()出錯返回EAGAIN

2)若未指定IPC_NOWAIT,則信號量的semncnt值加1(由於調用進程將進 入休眠狀態),而後調用進程被掛起直至:①此信號量變成大於或等於sem_op的絕對值;②從系統中刪除了此信號量,返回EIDRM;③進程捕捉到一個信 號,並從信號處理程序返回,返回EINTR。(與消息隊列的阻塞處理方式 很類似)

 3、信號量操做

semctl函數對一個信號量執行各類控制操做。

#include <sys/sem.h>

int semctl (int semid, int semnum, int cmd, /*可選參數*/ ) ;

第四個參數是可選的,取決於第三個參數cmd

參數semnum指定信號集中的哪一個信號(操做對象)

參數cmd指定如下10種命令中的一種,semid指定的信號量集合上執行此命令。

IPC_STAT   讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。

IPC_SET     設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。

IPC_RMID  將信號量集從內存中刪除。

GETALL      用於讀取信號量集中的全部信號量的值。

GETNCNT  返回正在等待資源的進程數目。

GETPID      返回最後一個執行semop操做的進程的PID

GETVAL      返回信號量集中的一個單個的信號量的值。

GETZCNT   返回這在等待徹底空閒的資源的進程數目。

SETALL       設置信號量集中的全部的信號量的值。

SETVAL      設置信號量集中的一個單獨的信號量的值。

下面來看個具體的應用。首先在/home/zhf/zhf/c_prj/test1.c上建立一個信號量,在模擬系統分配資源。沒隔3秒鐘就有一個資源被佔用。

Test6.c中的代碼:

#define RESOURCE 4

int sem_function_set()
{
    key_t key;
    int semid;
    struct sembuf sbuf={0,-1,IPC_NOWAIT};
    union semun arg;
    if ((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("ftok error!\n");
        exit(1);
    }
    if ((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("segmet error!\n");
        exit(1);
    }
    arg.val=RESOURCE;
    if(semctl(semid,0,SETVAL,arg)==-1)
    {
        perror("semctl errror!\n");
        exit(1);
    }
    while(1)
    {
        if (semop(semid,&sbuf,1)==-1)
        {
            perror("semop error!\n");
            exit(1);
        }
        sleep(3);
    }
    semctl(semid,0,IPC_RMID,0);
    exit(0);
}

test5.c中的代碼:

int sem_get()
{
    key_t key;
    int semid,semval;
    union semun arg;
    if((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("key errror1\n");
        return 1;
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("semget error!\n");
        return 1;
    }
    while(1)
    {
        if((semval=semctl(semid,0,GETVAL,0))==-1)
        {
            perror("semctl error!\n");
            exit(1);
        }
        if(semval > 0)
        {
            printf("%d resource could be used\n",semval);
        }
        else
        {
            printf("no resource could be used\n");
            break;
        }
        sleep(3);
    }
    exit(0);
}

 

分別在2個終端執行:結果以下

相關文章
相關標籤/搜索