[APUE]再读之进程间通信

来源:互联网 发布:与av淘宝一样的网站 编辑:程序博客网 时间:2024/06/13 12:05

本章主要介绍几种进程间通信的方式。管道,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 512struct 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 512struct 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 1024int 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 100int 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;}



0 0
原创粉丝点击