进程间通信

管道:
    管道是通过pipe函数创建的:
    #include <unistd.h>
    int pipe(int fd[2]);
        返回值:成功0,失败-1
    fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入

    fstat函数对管道的每一段都返回一个FIFO类型的文件描述符。可以用S_ISFIFO宏测试管道。

    程序创建一个从父进程到子进程的管道,并且父进程经由管道向子进程传送数据:
        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h>

        int main(void)
        {
            int n;
            int fd[2];
            pid_t pid;
            char line[MAXLINE];

            if(pipe(fd)<0)
                printf("pipe error\n");
            if((pid=fork())<0){
                printf("fork error\n");
            }
            else if(pid>0){
                close(fd[0]);
                write(fd[1],"hello wordld\n",12);
            }
            else
            {
                close(fd[1]);
                n=read(fd[0],line,MAXLINE);
                write(STDOUT_FILENO,line,n);    
            }
            exit(0);
        }
    经由管道从父进程向子进程传送数据

        #include "apue.h"
        #include <sys/wait.h>

        #define DEF_PAGER "/bin/more"   

        int main(int argc,char *argv[])
        {
            int n;
            int fd[2];
            pid_t pid;
            char *pager,*argv0;
            char line[MAXLINE];
            FILE *fp;

            if(argc!=2)
                printf("usage :a.out<pathname>\n");
            if((fp=fopen(argv[1],"r"))==NULL)
                printf("can't open %s\n",argv[1]);
            if(pipe(fd)<0)
                printf("pipe error\n");

            if((pid=fork())<0)
                printf("fork error\n");
            else if(pid>0)
            {
                close(fd[0]);
                while(fgets(line,MAXLINE,fp)!=NULL){
                    n=strlen(line);
                    if(write(fd[1],line,n)!=n)
                        printf("write error to pipe\n");
                }
                if(ferror(fp))
                    printf("fgets error\n");
                close(fd[1]);
                if(waitpid(pid,NULL,0)<0)
                    printf("waitpid error\n");
                exit(0);
            }
            else
            {
                close(fd[1]);
                if(fd[0]!=STDIN_FILENO){
                    if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)
                    {
                        printf("dup2 error to stdin\n");
                    }
                    close(fd[0]);
                }
                if((pager=getenv("PAGER"))==NULL)
                    paget=DEF_PAGER;
                if((argv0=sttchr(pager,'/'))!=NULL)
                    argv0++;
                else
                    argv0=pager;
                if(execl(pager,argv0,(char *)0)<0)
                    printf("execl error for %s",pager);
            }
            exit(0);
            }
    将文件复制到分页程序。

    函数的管道的实现:
        #include "apue.h"

        static int pfd1[2],pfd2[2];

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

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

        void WAIT_PARENT(void)
        {
            char c;

            if(read(pfd1[0],&c,1)!=1)
                printf("read error\n");
            if(c!='p')
                printf("WAIT_PARENT:incorrect data\n");
        }

        void WAIT_CHILD(void)
        {
            char c;

            if(read(pfd2[0],&c,1)!=1)
                printf("read error\n");
            if(c!='c')
                printf("WAIT_CHILD:incorrect data\n");
        }
    让父进程和子进程同步的例程

    我们在调用fork之前创建了两个管道。父进程在调用TELL_CHILD时,经由上一个管道写一个字符“p”,子进程在调用TELL_PARENT时,
    经由下一个管道写一个字符“c”。相应的WAIT_XXX函数调用read读一个字符,没有读到字符时则阻塞(休眠等待)。
        父进程             子进程
        pfd1[1]---------"p"-------------->pfd1[0]
        pfd2[0]<--------"c"---------------pfd2[1]

函数popen和pclose:
    常见的操作时创建一个连接到另一个进程的管道,然后读其输出或者向其输入端发送数据,为此,标准I/O库提供了两个函数popen和
    pclose。这两个函数实现的操作是:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令
    终止。
    #include <stdio.h>
    FILE *popen(const char *cmdstring,const char *type);
        返回值:成功,返回文件指针,出错,返回NULL
    int pclose(FILE *fp);
        返回值:成功,返回cmdstring的终止状态,出错,返回-1
    函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针。
    如果type是“r”,则文件指针连接到cmdstring的标准输出。
    如果type是“w”,则文件指针连接到cmdstring的标准输入。  

    父进程         cmdstring(子进程)
    fp <---------------------- stdout
        执行fp=popen(cmdstring,"r")

    父进程         cmdstring(子进程)
    fp ----------------------> stdin
        执行fp=popen(cmdstring,"w")


    pclose函数关闭标准I/O流,等待命令终止,然后返回shell的终止状态。如果shell不能被执行,则pclose返回的终止状态与shell已
    执行exit一样。

    cmdstring由Bourne shell以下列方式执行:
    sh -c cmdstring
    这表示shell将扩展cmdstring中的任何特殊字符。例如,可以使用:
    fp=popen("ls *.c","r");
    或者
    fp=popen("cmd 2>&1","r");

    用popen重写程序:
        #include "apue.h"
        #include <sys/wait.h>

        #define PAGER "${PAGER:-more}"

        int main(int argc,char *argv[])
        {
            char line[MAXLINE];
            FILE *fpin,*fpout;

            if(argc!=2)
                printf("usage:a.out <pathanme>\n");

            if((fpin=fopen(argv[1],"r"))==NULL)
                printf("can't open %s",argv[1]);
            if((fpout=popen(PAGER,"w"))==NULL)
                printf("popen error\n");

            while(fgets(line,MAXLINE,fpin)!=NULL)
            {
                if(fputs(line,fpout)==EOF)
                    printf("fputs error to pipe\n");
            }
            if(ferror(fpin))
                printf("fgets error\n");
            if(pclose(fpout)==-1)
                printf("pclose error\n");
            exit(0);
        }
    用popen向分页程序传送文件
    shell命令${PAGER:-more}的意思是:如果shell变量PAGER已经定义,且其值非空,则使用其值,否则使用字符串more。

    编写的popen和pclose:
        #include "apue.h"
        #include <errno.h>
        #include <fcntl.h>
        #include <sys/wait.h>

        /*
        *在运行时分配给数组的指针。
        */
        static pid_t    *childpid = NULL;

        /*
        *从我们的open_max()
        */
        static int maxfd;

        FILE *
        popen(const char *cmdstring, const char *type)
        {
            int      i;
            int      pfd[2];
            pid_t    pid;
            FILE    *fp;

            /* only allow "r" or "w" */
            if((type[0] != 'r' &&  type[0] != 'w') || type[1] != 0)
            {
            errno = EINVAL;    /* required by POSIX */
            return(NULL);
            }

            if(childpid == NULL)    /* 第一次通过 */
            {
            /* allocate zeroed out array for child pids */
            maxfd = open_max();
            if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
                return(NULL);
            }

            if(pipe(pfd) < 0)
            return(NULL);    /* errno set by pipe() */


            if((pid = fork()) < 0)
            {
            return(NULL);    /* error set by fork() */
            }
            else if(pid == 0)
            {
            if(*type == 'r')
            {
                close(pfd[0]);
                if(pfd[1] != STDOUT_FILENO)
                {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);    
                }
            }
            else
            {
                close(pfd[1]);
                if(pfd[0] != STDIN_FILENO)
                {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
                }
            }

            /* close all descriptors in childpid[] */
            for(i=0; i < maxfd; i++)
                if(childpid[i] > 0)
                close(i);

            execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
            _exit(127);
            }

            /* parent continues... */
            if(*type == 'r')
            {
            close(pfd[1]);
            if((fp = fdopen(pfd[0], type)) == NULL)
                return(NULL);
            }
            else
            {
            close(pfd[0]);
            if((fp = fdopen(pfd[1], type)) == NULL)//fdopen取一个现存的文件描述符,并使一个标准的I / O流与该描述符相结合
                return(NULL);
            }

            childpid[fileno(fp)] = pid;    /* remeber child pid for this fd */
            return(fp);
        }

        int
        pclose(FILE *fp)
        {
            int      fd, stat;
            pid_t    pid;

            if(childpid == NULL)
            {
            errno = EINVAL;
            return(-1);    /* popen() has never been called */
            }

            fd = fileno(fp);//fileno()用来取得参数stream指定的文件流所使用的文件描述符
            if((pid = childpid[fd]) = 0)
            {
            errno = EINVAL;
            return(-1);    /*  fp wasn't opened by popen() */
            }

            childpid[fd] = 0;
            if(fclose(fp) == EOF)
            return(-1);

            while(waitpid(pid, &stat, 0) < 0)
            if(errno != EINTR)
                return(-1);    /* error other than EINTR from waitpid() */

            return(stat);        /* return child's termination status */
        }
    一个简单的用于演示这个操作的过滤程序。它将标准输入复制到标准输出,在复制时将大写字符变换未小写字符。在写完换行符之后,要仔细
    冲洗(fflush)标准输出。
        #include "apue.h"
        #include <ctype.h>

        int main(void)
        {
            int c;

            while((c=getchar())!=EOF){
                if(isupper(c))
                    c=tolower(c);
                if(putchar(c)==EOF)
                    printf("output error\n");;
                if(c=='\n')
                    fflush(stdout);
            }
            exit(0);
        }
    编译为myuclc,然后使用popen调用它:
        #include "apue.h"
        #include <sys/wait.h>

        int main(void)
        {
            char line[MAXLINE];
            FILE *fpin;

            if((fpin=popen("myuclc","r"))==NULL)
                printf("popen error\n");

            for(;;){
                fputs("prompt>",stdout);
                fflush(stdout);
                if(fgets(line,MAXLINE,fpin)==NULL)
                    break;
                if(fputs(line,stdout)==EOF)
                    printf("fputs error to pipe\n");
            }
            if(pclose(fpin)==-1)
                printf("pclose error\n");
            putchar('\n');
            exit(0);
        }
    因为标准输出常常时行缓冲的,而提示并不包含换行符,所以在写了提示之后,需要调用fflush。

协同进程:
    UNIX系统过滤程序从标准输入读取数据,向标准输出写数据。当一个过滤程序既产生某个过滤程序的输入,
    又读取该过滤程序的输出时,它就变成了协同进程。

    一个简单的协同进程,它从其标准输入读取两个数,计算它们的和,然后将和写至其标准输出。

        #include "apue.h"

        int main(void)
        {
            int n,int1,int2;
            char line[MAXLINE];


            while((n=read(STDIN_FILENO,line,MAXLINE))>0){
                line[n]=0;//null终止
                if(sscanf(line,"%d%d",&int1,&int2)==2){
                    sprintf(line,"%d\n",int1+int2);
                    n=strlen(line);
                    if(write(STDOUT_FILENO,line,n)!=n)
                        printf("write error\n");
                }
                else{
                    if(write(STDOUT_FILENO,"invalid args\n",13)!=13)
                        printf("write error\n");
                }       
            }
            exit(0);
        }
    将两个数相加的简单过滤程序。

    对此程序进程编译,将其可执行代码存入名为add2的文件。
    程序从其标准输入读取两个数之后调用add2协同进程,并将协同进程送来的值写到其标准输出中。
        #include "apue.h"

        static void sig_pipe(int);

        int main(void)
        {
            int n,fd1[2],fd2[2];
            pid_t pid;
            char line[MAXLINE];

            if(signal(SIGPIPE,sig_pipe)==SIG_ERR)
                printf("signal error\n");

            if(pipe(fd1)<0 || pipe(fd2)<0)
                printf("pipe error\n");

            if((pid=fork())<0)
                printf("fork error\n");
            else if(pid>0)//parent
            {
                close(fd1[0]);  
                close(fd2[1]);

                while(fgets(line,MAXLINE,stdin)!=NULL)
                {
                    n=strlen(line);
                    if(write(fd1[1],line,n)!=n)
                        printf("write error to pipe\n");
                    if((n=read(fd2[0],line,MAXLINE))<0)
                        printf("read error from pipe\n");
                    if(n==0)
                    {
                        printf("child closed pipe\n");
                        break;
                    }
                    line[n]=0;
                    if(fputs(line,stdout)==EOF)
                        printf("fputs error\n");
                }
                if(ferror(stdin))
                    printf("fgets error on stdin\n");
                exit(0);
            }
            else{//child
                close(fd1[1]);
                close(fd2[0]);
                if(fd1[0]!=STDIN_FILENO)
                {
                    if(dup2(fd1[0],STDIN_FILENO)!=STDIN_FILENO)
                        printf("dup2 error to stdin\n");
                    close(fd1[0]);
                }
                if(fd2[1]!=STDOUT_FILENO){
                    if(dup2(fd2[1],STDOUT_FILENO)!=STDOUT_FILENO)
                        printf("dup2 error to stdout\n");
                    close(fd2[1]);
                }

                if(execl("./add2","add2",(char*)0)<0)
                    printf("execl error\n");
            }
            exit(0);
        }

        static void sig_pipe(int signo)
        {
            printf("SIGPIPE caught\n"); 
            exit(1);
        }
    父进程:
        fd1         fd2
    0   关闭读         读
    1   写           关闭写
    子进程:
        fd1         fd2
    0   读           关闭读 
    1   关闭写         写

    如果使用标准I/O改写该协同进程:
        #include <unistd.h>
        #include <stdlib.h>
        #include <stdio.h>

        #define MAXLINE 1024

        int main(void)
        {
            int int1,int2;
            char line[MAXLINE];

            while(fgets(line,MAXLINE,stdin)!=NULL)
            {
                if(sscanf(line,"%d%d",&int1,&int2)==2)
                {
                    if(printf("%d\n",int1+int2)==EOF)
                        printf("printf error\n");
                }
                else{
                    if(printf("invalid args\n")=EOF)
                        printf("printf error\n");
                }
            }
            exit(0);
        }

FIFO:
    #include <sys/stat.h>
    int mkfifo(const char *path,mode_t mode);
    int mkfifoat(int fd,const char *path,mode_t mode);
        返回值:成功0,失败-1

XSI IPC:
    ftok提供的唯一服务就是由一个路径名和项目ID产生一个键:
    #include <sys/ipc.h>
    key_t ftok(const char *path,int id);
        返回值:成功,返回键,出错,返回(key_t)-1
    path参数必须引用一个现有的文件。当产生键时,只使用id参数的低8位。


消息队列:
    msgget用于创建一个新队列或者打开一个现有队列。
    msgsnd将新消息添加到队列尾端。
    每个消息包含一个正的长整型类型的字段、一个非负的长度以及实际数据字节数,所有这些都在将消息添加
    到队列时,传送给msgsnd。
    msgrcv从队列中取消息。

    每个队列都有一个msqid_ds结构于其相关联:
    struct msqid_ds{
        struct ipc_perm msg_perm;
        msgqnum_t msg_qnum;//队列上的消息
        msglen_t msg_qbytes;//队列中的最大字节数
        pid_t msg_lspid;//pid of last msgsnd
        pid_t msg_lrpid;//pid of last msgrcv
        time_t msg_stime;//last-msgsnd time
        time_t msg_rtime;//last-msgrcv time
        time_t msg_ctime;//last change time
    };

    #include <sys/msg.h>
    int msgset(key_t key,int flag);
        返回值:成功,返回消息队列ID,出错,返回-1

    #include <sys/msg.h>
    int msgctl(int msgid,int cmd,struct msgid_ds *buf);
        返回值:成功,返回0,出错,返回-1
    cmd参数指定对msgid指定的队列要执行的命令:
    IPC_STAT    取此队列的msgid_ds结构,并将它存放在buf执行的结构中
    IPC_SET     将字段msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes
            从buf指向的结构复制到与这个队列相关的msgid_ds结构中。

    IPC_RMID    从系统中删除该消息队列以及仍在该队列中的所有数据。

    调用msgsnd将数据放到消息队列中:
    #include <sys/msg.h>
    int msgsnd(int msgid,const void *ptr,size_t nbytes,int flag);
        返回值:成功0,失败-1

    ptr指向mymesg结构的指针:
    struct mymesg{
        long mtype;
        char mtext[[512];
    };

    msgrcv从队列中取用消息:
    #include <sys/msg.h>
    ssize_t msgrcv(int msgid,void *ptr,size_t nbytes,long type,ing flag);
        返回值:成功,返回消息数据部分的长度,出错,返回-1

信号量:
    调用函数semget来获得一个信号量ID:
    #include <sys/sem.h>
    int semget(key_t key,int nsems,int flag);
        返回值:成功,返回信号量ID,出错,返回-1

    信号量的相关操作:
    #include <sys/sem.h>
    int semctl(int semid,int semnum,int cmd,...);


    函数semop自动执行信号量集合上的操作数组:
    #include <sys/sem.h>
    int semop(int semid,struct sembuf semoparray[],size_t mops);
    参数semoparray是一个指针,它指向一个由sembuf结构表示的信号量操作数组。
    struct sembuf
    {
      unsigned short int sem_num;   /* 信号量数 */
      short int sem_op;     /* 信号量操作 */
      short int sem_flg;        /* 操作标志 */
    };

共享存储:
    允许两个或者多个进程共享一个给定的存储区。   

    struct shmid_ds{
          struct ipc_perm shm_perm;/* 操作权限*/
          int shm_segsz;             /*段的大小(以字节为单位)*/
          time_t shm_atime;          /*最后一个进程附加到该段的时间*/
          time_t shm_dtime;          /*最后一个进程离开该段的时间*/
          time_t shm_ctime;          /*最后一个进程修改该段的时间*/
          unsigned short shm_cpid;   /*创建该段进程的pid*/
          unsigned short shm_lpid;   /*在该段上操作的最后1个进程的pid*/
          short shm_nattch;          /*当前附加到该段的进程的个数*/
        /*下面是私有的*/
          unsigned short shm_npages;  /*段的大小(以页为单位)*/
          unsigned long *shm_pages;   /*指向frames->SHMMAX的指针数组*/
          struct vm_area_struct *attaches; /*对共享段的描述*/
    };

    struct ipc_perm
    {
        key_t        key;          //调用shmget()时给出的关键字
        uid_t           uid;       /*共享内存所有者的有效用户ID */
        gid_t          gid;        /* 共享内存所有者所属组的有效组ID*/ 
        uid_t          cuid;       /* 共享内存创建 者的有效用户ID*/
        gid_t         cgid;        /* 共享内存创建者所属组的有效组ID*/
        unsigned short   mode;     /* Permissions + SHM_DEST和SHM_LOCKED标志*/
        unsignedshort    seq;      /* 序列号*/
    };


    调用的第一个函数通常是shmget,它获得一个共享存储标识符:
    #include <sys/shm.h>
    int shmget(key_t key,size_t size,int flag);
        返回值:成功,返回共享存储ID,出错,返回-1

    shmctl函数对共享存储段执行多种操作:
    #include <sys/shm.h>
    int shmctl(int shmid,int cmd,struct shmid_ds *buf);
        返回值:成功,返回0,出错,返回-1

    一旦创建一个共享存储段,进程就可以调用shmat将其连接到它的地址空间中:
    #include <sys/shm.h>
    void *shmat(int  shmid,const void *addr,int flag);
        返回值:成功,返回指向共享存储段的指针,失败,返回-1

    把共享内存区对象映射到调用进程的地址空间:
    #include <sys/shm.h>
    void *shmat(int shmid, const void *addr, int flg)
        成功:附加好的共享内存地址.出错:-1,错误原因存于errno中
    shmid   
    共享内存标识符
    shmaddr
    指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
    shmflg
    SHM_RDONLY:为只读模式,其他为读写模式

    shmdt(断开共享内存连接)
    #include <sys/shm.h>
    int shmat(const void *addr);
    addr参数是调用shmat时的返回值。如果成功,shmat将使相关shmid_ds结构中的shm_nattch计数器值减1

    程序打印了一些特定系统存放各种类型的数据的位置信息:
        #include <unistd.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <sys/shm.h>

        #define ARRAY_SIZE 40000
        #define MALLOC_SIZE 100000
        #define SHM_SIZE 100000
        #define SHM_MODE 0600 //user read/write

        char array[ARRAY_SIZE];

        int main(void)
        {
            int shmid;
            char *ptr,*shmptr;

            printf("array[] from %p to %p\n",(void*)&array[0],(void*)&array[ARRAY_SIZE]);
            printf("stack around %p\n",(void*)&shmid);

            if((ptr=malloc(MALLOC_SIZE))==NULL)
                printf("mallc error\n");
                printf("malloced from %p to %p\n",(void*)ptr,(void *)ptr+MALLOC_SIZE);

            if((shmid=shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE))<0)
                printf("shmat error\n");
            if((shmptr=shmat(shmid,0,0))==(void*)-1)
                printf("shmat error\n");
            printf("shared memory attached from %p to %p\n",(void *)shmptr,(void*)shmptr+SHM_SIZE);
            if(shmctl(shmid,IPC_RMID,0)<0)
                printf("shmctl error\n");
            exit(0);
        }
                打印各种类型的数据存放的位置
        $ ./exaple9
        array[] from 0x6010a0 to 0x60ace0
        stack around 0x7fffaa3a79fc
        malloced from 0x1ba4010 to 0x1bbc6b0
        shared memory attached from 0x7fdf3f8a9000 to 0x7fdf3f8c16a0

/dev/zero的存储映射:
    共享存储可由两个不相关的进程使用。但是,如果进程是相关的,则某些实现提供了一种不同的技术。

    在读取设备/dev/zero时,该设备是0字节的无限资源。它也接受写向它的任何数据,但又忽略这些数据。我们对此设备作为IPC的兴趣在于,
    当对其进行存储映射时,它具有一些特殊性质:
    1、创建一个未命名的存储区,其长度时mmap的第二个参数,将其向上取整为系统的最近页长。
    2、存储区都初始化为0
    3、如果多个进程的共同祖先进程对mmap指定了MAP_SHARED标志,则这些进程可共享此存储区

    原型:
    void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

    mmap将一个文件或者其它对象映射进内存。
    文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。  
    mmap在用户空间映射调用系统中作用很大。   

    程序是使用此特殊设备的一个例子:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/mman.h>

    #define NLOOPS 1000
    #define SIZ sizeof(long)/*共享内存区域的大小*/

    static int update(long *ptr)
    {
        return((*ptr)++);/*增量前返回值*/
    }

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


    int main(void)
    {
        int fd,i,counter;
        pid_t pid;
        void *area;

        if((fd=open("/dev/zero",O_RDWR))<0)
            printf("open error\n");
        //成功执行时,mmap()返回被映射区的指针
        if((area=mmap(0,SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
            printf("mmap error\n");
        close(fd);

        TELL_WAIT();

        if((pid=fork())<0)
            printf("fork error\n');
        else if(pid>0)/*parent*/
        {
            for(i=0;i<NLOOPS;i+=2)
            {
                if((counter=update((long*)area))!=i)
                    printf("parent:expected %d ,got %d",i,counter);
                TELL_CHILD(pid);
                WAIT_CHILD();
            }
        }
        else{//child
            for(i=1;i<NLOOPS+1;i+=2){
                WAIT_PARENT();

                if((counter=update((long*)area))!=i)
                    printf("child:expected %d,got %d",i,counter);
                TELL_PARENT(getppid());
            }
        }
        exit(0);
    }
    在父进程、子进程之间使用/dev/zero的存储映射I/O的IPC

匿名存储映射:
    为使上图中的程序应用这个设施,我们对它进行3处修改:
    a.删除了/dev/zero的open语句
    b.删除了fd的close语句
    c.将mmap调用修改如下:
    if((area=mmap(0,SIZE,PROT_READ | PROT_WRITE,MAP_ANON | MAP_SHARED,-1,0))==MAP_FAILED)

    此调用执行了MAP_ANON标志,并将文件描述符设置为-1.

POSIX信号量:
    3种IPC机制源于POSIX.1的实时扩展。Single UNIX Specificationa将3种机制(消息队列、信号量和共享存储)置于可选部分中。
    在SUSv4之前,POSIX信号量接口已经被包含在信号量选项中。在SUSv4中,这些接口被移至基本规范,而消息队列和共享存储接口依然
    是可选的。

    我们可以调用sem_open函数来创建一个新的命名信号量或者使用一个现有信号量:
    #include <semaphore.h>
    sem_t *sem_open(const char *name,int oflag,.../*mode_t mode,unsigned int value*/);
        返回值:成功,返回指向信号量的指针,出错,返回SEM_FAILED
    存在一个现有的命名信号量时:我们只指定两个参数,信号量的名字和oflag参数的0值
    当oflag参数有O_CREAT标志集时,如果命名信号量不存在,则创建一个新的。
    如果它已经存在,则会被使用,但是不会有额外的初始化发生。

    调用sem_close函数来释放任何信号量相关的资源:
    #include <semaphore.h>
    int sem_close(sem_t *sem);
        返回值:成功,返回0,出错,返回-1
    如果进程没有首先调用sem_close而退出,那么内核将自动关闭任何打开的信号量。
    注意,这不会影响信号量的状态---如果已经对它进行了增1操作,并不会仅因为退出而改变。

    sem_unlink函数来销毁一个命名信号量:
    #include <semaphore.h>
    int sem_unlink(const char *name);
        返回值:成功,返回0,失败,返回-1
    sem_unlink函数删除信号量的名字。
    如果没有打开的信号量引用,则该信号量会被销毁。
    否则,销毁会延迟到最后一个打开的引用关闭。

    可以使用sem_wait或者sem_trywait函数来实现信号量的减1操作:
    #include <semaphore.h>
    int sem_wait(sem_t *sem);
    int sem_trywait(sem_t *sem);
        返回值:成功0,失败,-1
    使用sem_wait函数时,如果信号量计数是0就会被阻塞。直到成功使信号量减1或者被信号中断才返回。
    可以使用sem_trywait函数来避免阻塞。
    调用sem_trywait时,如果信号量是0,则不会阻塞,而是会返回-1并且将errno置为EAGAIN。

    第三个选择是阻塞一段确定的时间。为此,可以使用sem_timewait函数:
    #include <semaphore.h>
    #include <time.h>

    int sem_timewait(sem_t *restrict sem,const struct timespec *restrict tsptr);
        返回值:成功0,失败,-1

    调用sem_post函数使信号量值增1:
    #include <semaphore.h>
    int sem_post(sem_t *sem);
        返回值:成功,0,出错,返回-1

    调用sem_init函数创建一个未命名的信号量:
    #include <semaphore.h>
    int sem_init(sem_t *sem,int pshared,unsigned int value);
        返回值:成功0,失败-1
    pshared参数表明是否在多个进程中使用信号量。如果是,将其设置成一个非0值。
    value参数指定了信号量的初始值
    sem_t类型的变量并把它的地址传递给sem_init来实现初始化,而不是像sem_open函数那样返回一个指向信号量的指针。
    如果要在两个进程之间使用信号量,需要确保sem参数指向两个进程之间共享的内存范围。

    调用sem_destroy函数丢弃它:
    #include <semaphore.h>
    int sem_destroy(sem_t *sem);
        返回值:成功,0,失败,-1
    调用sem_destroy后,不能再使用任何带有sem的信号量函数,除非通过调用sem_init重新初始化它。

    sem_getvalue函数可以用来检索信号量值:
    #include <semaphore.h>
    int sem_getvalue(sem_t *restrict sem,int *restrict valp);
        返回值:成功,0,失败,-1
    成功后,valp指向的整数值将包含信号量值。

    程序展示了基于信号量的互斥原语的实现:
        #include <unistd.h>
        #include <stdlib.h>
        #include <stdio.h>
        #include <errno.h>

        struct slock{
            sem_t *semp;
            char name[_POSIX_NAME_MAX];
        }

        struct slock * s_alloc(){
            struct slock *sp;
            static int cnt;

            if((sp=malloc(sizeof(struct slock)))==NULL)
                return(NULL);
            do{
                snprintf(sp->name,sizeof(sp->name),"/%ld.%d",(long)getpid(),cnt++);
                sp->semp=sem_open(sp->name,O_CREAT|O_EXCL,S_IRWXU,1);
            }while((sp->semp==SEM_FAILED)&&(errno==EEXIST));

            if(sp->semp==SEM_FAILED){
                free(sp);
                return(NULL);
            }
            sem_unlink(sp->name);
            return(sp);
        }

        void s_free(struct slock *sp)
        {
            sem_close(sp->semp);
            free(sp);
        }

        int s_lock(struct slock *sp)
        {
            return(sem_wait(sp->semp));
        }

        int s_trylock(struct slock *sp)
        {
            return(sem_trywait(sp->semp));
        }

        int s_unlock(struct slock *sp)
        {
            return(sem_post(sp->semp));
        }
    根据进程ID和计数器来创建名字。我们不会可以用互斥量区保护计数器,因为当两个竞争的线程同时调用s_alloc并以
    同一个名字结束时,在调用sem_open中使用O_EXCL标志将会使其中一个线程成功而另一个线程失败,失败的线程会将
    errno设置未EEXIST,所以对于这种情况,我们只是再次尝试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值