[APUE]再讀之進程間通信

本章主要介紹幾種進程間通信的方式。管道,FIFO(也叫命名管道), 消息隊列,信號量,共享存儲。

其他的不在本章內容中的進程間通信方式有:流管道,命令流管道(下章介紹),套接字,流(後兩種支持在不同主機間的進程通信)。

1. 管道

管道只能在擁有公共祖先間進程通信使用,並且管道是半雙工的。

#include <unistd.h>
int pipe(int fields[2])
field[0]爲讀,field[1]爲寫。通常父子進程一個關閉讀端,一個關閉寫端。

當一個管道讀端被關閉時,再向寫端寫數據將會產生SIGPIPE。當一個管道寫端杯關閉時,所有數據被讀完後,read 返回0.以指示到達了文件末尾。

簡單的一個例子,子進程發送一條消息給主進程。沒有數據時候,主進程的read將會一直block.

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main()
{
    pid_t pid;
    int fd[2];
    if(pipe(fd)<0)
    {
        printf("pipe error\n");
        return -1;
    }
    if((pid=fork())<0)
    {
        printf("fork error\n");
    }
    else if (pid==0)
    {
        close(fd[0]);
        char* buf = "message write to parent";
        sleep(5);
        if(write(fd[1],buf, strlen(buf))!=strlen(buf))
        {
            printf("Write to pipe error\n");
            return -1;
        }
    }
    else
    {
        close(fd[1]);
        char bufp[1024];
        int n;
        printf("Begin to read\n");
        if((n= read(fd[0],bufp, 1024))==-1)
        {
            printf("read error\n");
        }
        //bufp[n] = "\0";
        printf("Read content from child is %s\n", bufp);
    }

}
    


利用管道,父進程讀文件,然後子進程輸出的例子:

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char* argv[])
{
    pid_t pid;
    int fd[2];
    FILE* fp;
    if (argc<2)
    {
        printf("No args\n");
        return -1;
    }
    if(pipe(fd)<0)
    {
        printf("pipe error\n");
        return -1;
    }

    if((pid=fork())<0)
    {
        printf("fork error\n");
        return -1;
    }
    else if (pid == 0) //child
    {
        close(fd[1]);
        if (STDIN_FILENO!=fd[0])
        {
            if (dup2(fd[0],STDIN_FILENO)!= STDIN_FILENO)
            {
                printf("dup error\n");
                return -1;
            }
            close(fd[0]);
        }
        if (execlp("more","more",(char*)0)<0)
        {
            printf("execlp error\n");
        }
        else
        {
            printf("execlp succeed\n");
        }


    }
    else
    {
        close(fd[0]);
        char line[4096];
        int n;
        fp = fopen(argv[1],"r");
        if(fp==NULL)
        {
            printf("fopen error\n");
            return -1;
        }
        while(fgets(line,4096,fp)!=NULL)
        {
            n = strlen(line);
            if(write(fd[1],line,n)!=n)
            {
                printf("write pipe error\n");
                return -1;
            }
        }
        if (ferror(fp))
        {
            printf("fgets error");
            return -1;
        }
        close(fd[1]);
        if((waitpid(pid,NULL,0))<0)
        {
            printf("wait error");
            return -1;
        }
        exit(0);
    }
}
管道版進程同步函數,作者有些牽強,只是爲了寫管道的用法而寫,一般人估計不會用這種方式同步。

#include <unistd.h>
#include <stdio.h>

int fd1[2];
int fd2[2];

void TELL_WAIT()
{
    if(pipe(fd1)<0 || pipe(fd2)<0)
    {
        printf("pipe error\n");
    }
}

void TELL_CHILD()
{
    if(write(fd1[1],"p",1)!=1)
        printf("write error\n");
}

void WAIT_CHILD()
{
    char c;
    if(read(fd2[0],&c,1)!=1)
        printf("write error\n");
    if(c!='c')
        printf("get wrong char\n");
}

void TELL_PARENT()
{
    if(write(fd2[1],"c",1)!=1)
        printf("write error\n");
}

void WAIT_PARENT()
{
    char c ;
    if(read(fd1[0],&c,1)!=1)
        printf("write error\n");
    if(c!='p')
        printf("get wrong char\n");
}


int main()
{
    TELL_WAIT();   
    pid_t pid;
    if ((pid=fork())<0)
    {
        printf("fork error\n");
        return -1;
    }
    else if(pid==0)
    {
        WAIT_PARENT();
        printf("message from child\n");
        TELL_PARENT();
    }
    else
    {
        printf("message from parent\n");
        TELL_CHILD();
        WAIT_CHILD();
        printf("message from parent2\n");
    }
}
popen 的demo

#include <stdio.h>
#include <string.h>

int main()
{
    FILE* fp = popen("ls -lt /home","r");
    char buf[2048];
    while((fgets(buf,2048,fp)!=NULL))
    {
        int n = strlen(buf);
        buf[n] = '\0';
        printf("%s", buf);
    }
    pclose(fp);
}

2. fifo.

沒有制定,O_NONBLOCK選項時,讀進程會阻塞到某一個寫進程打開fifo. 指定 O_NONBLOCK時, 如果沒有寫進程打開FIFO, 則讀進程立即返回。如果沒有爲讀而打開一個FIFO,那麼只寫操作將返回錯誤,errno 是ENXIO.

和管道一樣,如果沒有讀進程,那寫操作將產生SIGPIPE,如果最後一個寫進程關閉,則爲該FIFO產生一個文件結束符標識。

使用FIFO的時候需要注意使用UNLINK 銷燬fifo,不然即使進程結束FIFO 也會存在系統中。如下代碼,沒有unlink時候,第二次執行將出錯。

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <stdio.h>

#define fifo_name "/tmp/tf1.fifo"
#define FILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)

int main()
{
    char errorbuf[2048];
    if(mkfifo(fifo_name,FILEMODE )==-1)
    {
        snprintf(errorbuf,sizeof(errorbuf), "can not create %s\n",fifo_name);
        perror(errorbuf);
    }
    unlink(fifo_name);
}


FIFO 的服務器客戶端版本

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

#define fifo_server "/tmp/fifo_server"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)

int main()
{
    char buferror[1024];
    char buf[1024];
    int fd;
    if((mkfifo(fifo_server,FILE_MODE  )==-1) && errno!= EEXIST)
    {
        snprintf(buferror,sizeof(buferror), "create %s error\n",fifo_server);
        return -1;
    }
    if((fd=open(fifo_server, O_RDONLY ))==-1)
    {
        snprintf(buferror,sizeof(buferror), "open %s error\n",fifo_server);
        return -1;
    }
    int n;
    while(1)
    {
        n =read(fd, buf,sizeof(buf));
        if (n<1)
            continue;
        if (buf[strlen(buf)-1]=='\n')
            buf[strlen(buf)-1] = '\0';
        int pid = atoi(buf);
        printf("Get message from %d\n", pid);
    }

}

client

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#define fifo_server "/tmp/fifo_server"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)

int main()
{
    pid_t pid = getpid();
    char buf[1024];
    snprintf(buf,sizeof(buf), "%d\n",pid);
    int fd;
    if((fd=open(fifo_server, O_WRONLY))<0)
    {
        printf("open error\n");
        return -1;
    }

    if(write(fd, buf, strlen(buf))!= strlen(buf))
    {
        printf("write error\n");
        return -1;
    }
}

3. 信號量,消息隊列和共享存儲

三種內核IPC結構都使用非負整數作爲唯一標識符。到達最大整數後又從0開始計數。

//ftok創建唯一IPC key
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char* fname,int id)

創建IPC 的方法有兩種:a) 使用shmget, semget,msgget 的key 爲IPC_PRIVATE 或者 b) 使用shmget,semget,msgget的flag爲IPC_CREAT 並且key 唯一。

IPC的權限結構:

struct ipc_perm{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode; /*access modes*/
ulong seq; /* slot usage sequence number*/
key_t key; /*key*/
}

消息隊列的優缺點:1. 和其他進程通信不同,IPC會有殘留 2. 增加了其他很多API函數


4. 消息隊列demo

Server 部分,創建消息隊列,一直等待client傳遞消息,直到遇到end消息後,刪除消息隊列並返回。

#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MSG_LEN 512
struct mymsg
{
    long mbytes;
    char text[MSG_LEN];
};
int main()
{
    char buf[2048];
    //get or create message id
    key_t key = ftok("/tmp/abc/",1);  
    int id;
    id=msgget(key, 0666| IPC_CREAT);
    if(id==-1)
    {
        snprintf(buf,2048, "Creat key msg error:");
        perror(buf);
        return -1;
    }
    //wait for the message
    while(1)
    {
        struct mymsg msg;
        if((msgrcv(id, (void*) &msg,MSG_LEN ,0,0))==-1)
        {
            printf("receive message error\n");
            return -1;
        }
        printf("Received message:%s\n", msg.text);
        if(strcmp(msg.text,"end")==0)
            break;
        
    }
    if(msgctl(id, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS); 
}

client 部分,發送兩個消息,第一個爲正常發送消息,第二個爲結束消息。

#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define MSG_LEN 512

struct mymsg
{
    long mbytes;
    char text[MSG_LEN];
};
int main()
{
    char buf[2048];
    //create get the msg queue
    key_t key = ftok("/tmp/abc/",1);  
    printf("key is %d\n", key);
    int id;
    if((id=msgget(key, 0600| IPC_CREAT))==-1)
    {
        snprintf(buf,2048, "Creat key msg error\n");
        perror(buf);
    }
    //send first message
    struct mymsg msg;
    const char sendmsg[] = "fist msg to send\n";
    snprintf(msg.text,MSG_LEN,sendmsg );
    msg.mbytes = sizeof(sendmsg);
    if(msgsnd(id,(void*)&msg,sizeof(sendmsg),0)==-1)
    {
        memset(buf,sizeof(buf),0);
        snprintf(buf,2048, "send first msg error\n");
        perror(buf);
    }
    //send second message
    struct mymsg msg2;
    const char sendmsg2[] = "end";
    snprintf(msg2.text,MSG_LEN,sendmsg2 );
    msg2.mbytes = sizeof(sendmsg2);
    if(msgsnd(id,(void*)&msg2,sizeof(sendmsg2),0)==-1)
    {
        memset(buf,sizeof(buf),0);
        snprintf(buf,2048, "send second msg error\n");
        perror(buf);
    }

}


5. 信號量

信號量一般用作進程間同步,或進程鎖。一般只要0,1開關信號量。系統實現的信號量有些複雜。

信號量的demo. 主要使用IPC_UNDO, 這個標識可以使得即使程序異常終止,也不會因爲以前的改動造成程序一直死鎖。

運行方法如下: ./a.out new OOO &  ; /a.out anything XXX. 則可看到屏幕上OXOX交叉打印了。

最後可運行.  ./a.out del  anything 來刪除系統中的信號量。

#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

union semun
{
    int val;
    struct semid_ds *buf;
    ushort *array;
};

static int ctlSemaphore(int sem_id, int op_number)
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = op_number;
    buf.sem_flg = SEM_UNDO;
    if(semop(sem_id,&buf,1)==-1)
    {
        fprintf(stderr,"operate semaphore failed\n");
        return 0;
    }
    return 1;
}

static int iniSemaphore(int sem_id)
{
    union semun sem_union;
    sem_union.val =1;
    if(semctl(sem_id,0,SETVAL,sem_union)==-1)
    {
        fprintf(stderr,"initial semaphore failed\n");
        return 0;
    }
    return 1;
}

static void del_semvalue(int sem_id)  
{  
    union semun sem_union;  
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)  
    {
        fprintf(stderr, "Failed to delete semaphore\n");  
    } 
}

int main(int argc, char* argv[])
{
    if (argc<3)
    {
        printf("At least two arguments\n");
        return -1;
    }
    //create the semophore key
    key_t key = ftok(".",5);
    int semid = semget(key,1,0666|IPC_CREAT);
    if(semid==-1)
    {
        printf("Create or get semophore failed. errno = %d, error is :%s\n", errno,strerror(errno));
        return -1;
    }

    //if del , we delete the semaphore
    if(strcmp(argv[1],"del")==0)
    {
        del_semvalue(semid);
        return -1;
    }

    //if new, we need to initial the semaphore
    if(strcmp(argv[1],"new")==0)
    {
        if(0==iniSemaphore(semid))
           return -1;
    }
    int i;
    for (i=0;i<10;i++)
    {
       ctlSemaphore(semid,-1);
       printf("%c",argv[2][0]);
       fflush(stdout);
       sleep(2);
       ctlSemaphore(semid,1);
       sleep(2);
    }

}

6. 共享內存

最快的一種進程間通信方式。常用的父子進程間通信的demo, 父進程得到子進程的消息並打印。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#define SIZE 1024

int main()
{
    int shmid;
    char* shmaddr;
    struct shmid_ds buf;
    int flag =0;
    int pid;

    //0 get share memory
    shmid = shmget(IPC_PRIVATE,SIZE,IPC_CREAT| 0600);
    if(shmid<0)
    {
        perror("get shm ipc_di error");
        return -1;
    }
    shmaddr = (char*) shmat(shmid,NULL,0);
    if((int)shmaddr==-1)
    {
        perror("shmat addr error");
        return -1;
    }
    pid = fork();
    if(pid==0)
    {
        strcpy(shmaddr,"Message from child\n");
        shmdt(shmaddr);
        return 0;
    }
    else if (pid>0)
    {
        sleep(3);
        printf("%s",shmaddr);
        shmdt(shmaddr);
        if(shmctl(shmid,IPC_RMID,NULL)==-1)
            printf("remove shared memory failed\n");
    }
}

7. 內存映射

mmap.

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);int munmap(void* start,size_t length) 出錯返回(void*) -1

start 爲映射去的開始地址。0 表示自動分配。 length爲映射區長度。prot, 期望的內存保護標識。PROT_READ,PROT_WRITE分別爲讀寫。

flags爲映射對象類型,常用爲MAP_SHARED, MAP_PRIVATE 等。

fd爲映射文件標識符。 offset提示映射從那個位置開始。


通過內存映射改寫文件內容demo:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main()
{
    int fd = open("test.log",O_RDWR);
    if(fd<0)
    {
        printf("Open log error\n");
        return -1;
    }
    int size;
    struct stat statbuf;
    if(fstat(fd,&statbuf)==-1)
    {
        printf("stat error\n");
        return -1;
    }
    char* mapped;
    mapped= mmap(NULL,statbuf.st_size,PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
    if(mapped==(void*)-1)
    {
        printf("map memory error\n");
        return -1;
    }
    close(fd); 
    printf("%s", mapped);
    mapped[1] = 'b';
    if ((msync((void *)mapped, statbuf.st_size, MS_SYNC)) == -1) {  
        perror("msync");  
        return -1;
    }  
    if ((munmap((void *)mapped, statbuf.st_size)) == -1) {  
        perror("munmap");  
    }
    return 0;
}

mmap父子進程通信

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 100
int main()
{
    char* mapped;
    mapped= mmap(NULL,BUF_SIZE ,PROT_READ| PROT_WRITE , MAP_SHARED | MAP_ANON ,-
1,0);
    if(mapped==(void*)-1)
    {
        printf("map memory error\n");
        return -1;
    }

    pid_t pid;
    pid = fork();
    if(pid<0)
    {
        printf("fork error\n");        
        return -1;
    }

    if(pid==0)
    {
        sprintf(mapped,"%s","message from child");
        sleep(5);
        exit(0);
    }

    sleep(2);
    printf("%s\n",mapped);
    if ((munmap((void *)mapped,BUF_SIZE )) == -1) {
        perror("munmap");
    }
    return 0;
}



發佈了45 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章