linux c編程:進程間通信


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

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

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

(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;
}

運行結果:

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

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個終端執行:結果如下

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM