• 进程间通信


    管道:
        管道是通过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
    ");
                if((pid=fork())<0){
                    printf("fork error
    ");
                }
                else if(pid>0){
                    close(fd[0]);
                    write(fd[1],"hello wordld
    ",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>
    ");
                if((fp=fopen(argv[1],"r"))==NULL)
                    printf("can't open %s
    ",argv[1]);
                if(pipe(fd)<0)
                    printf("pipe error
    ");
    
                if((pid=fork())<0)
                    printf("fork error
    ");
                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
    ");
                    }
                    if(ferror(fp))
                        printf("fgets error
    ");
                    close(fd[1]);
                    if(waitpid(pid,NULL,0)<0)
                        printf("waitpid error
    ");
                    exit(0);
                }
                else
                {
                    close(fd[1]);
                    if(fd[0]!=STDIN_FILENO){
                        if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)
                        {
                            printf("dup2 error to stdin
    ");
                        }
                        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
    ");
            }
    
            void TELL_PARENT(pid_t pid)
            {
                if(write(pfd2[1],"c",1)!=1)
                    printf("write error
    ");
            }
    
            void WAIT_PARENT(void)
            {
                char c;
    
                if(read(pfd1[0],&c,1)!=1)
                    printf("read error
    ");
                if(c!='p')
                    printf("WAIT_PARENT:incorrect data
    ");
            }
    
            void WAIT_CHILD(void)
            {
                char c;
    
                if(read(pfd2[0],&c,1)!=1)
                    printf("read error
    ");
                if(c!='c')
                    printf("WAIT_CHILD:incorrect data
    ");
            }
        让父进程和子进程同步的例程
    
        我们在调用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>
    ");
    
                if((fpin=fopen(argv[1],"r"))==NULL)
                    printf("can't open %s",argv[1]);
                if((fpout=popen(PAGER,"w"))==NULL)
                    printf("popen error
    ");
    
                while(fgets(line,MAXLINE,fpin)!=NULL)
                {
                    if(fputs(line,fpout)==EOF)
                        printf("fputs error to pipe
    ");
                }
                if(ferror(fpin))
                    printf("fgets error
    ");
                if(pclose(fpout)==-1)
                    printf("pclose error
    ");
                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
    ");;
                    if(c=='
    ')
                        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
    ");
    
                for(;;){
                    fputs("prompt>",stdout);
                    fflush(stdout);
                    if(fgets(line,MAXLINE,fpin)==NULL)
                        break;
                    if(fputs(line,stdout)==EOF)
                        printf("fputs error to pipe
    ");
                }
                if(pclose(fpin)==-1)
                    printf("pclose error
    ");
                putchar('
    ');
                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
    ",int1+int2);
                        n=strlen(line);
                        if(write(STDOUT_FILENO,line,n)!=n)
                            printf("write error
    ");
                    }
                    else{
                        if(write(STDOUT_FILENO,"invalid args
    ",13)!=13)
                            printf("write error
    ");
                    }       
                }
                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
    ");
    
                if(pipe(fd1)<0 || pipe(fd2)<0)
                    printf("pipe error
    ");
    
                if((pid=fork())<0)
                    printf("fork error
    ");
                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
    ");
                        if((n=read(fd2[0],line,MAXLINE))<0)
                            printf("read error from pipe
    ");
                        if(n==0)
                        {
                            printf("child closed pipe
    ");
                            break;
                        }
                        line[n]=0;
                        if(fputs(line,stdout)==EOF)
                            printf("fputs error
    ");
                    }
                    if(ferror(stdin))
                        printf("fgets error on stdin
    ");
                    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
    ");
                        close(fd1[0]);
                    }
                    if(fd2[1]!=STDOUT_FILENO){
                        if(dup2(fd2[1],STDOUT_FILENO)!=STDOUT_FILENO)
                            printf("dup2 error to stdout
    ");
                        close(fd2[1]);
                    }
    
                    if(execl("./add2","add2",(char*)0)<0)
                        printf("execl error
    ");
                }
                exit(0);
            }
    
            static void sig_pipe(int signo)
            {
                printf("SIGPIPE caught
    "); 
                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
    ",int1+int2)==EOF)
                            printf("printf error
    ");
                    }
                    else{
                        if(printf("invalid args
    ")=EOF)
                            printf("printf error
    ");
                    }
                }
                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
    ",(void*)&array[0],(void*)&array[ARRAY_SIZE]);
                printf("stack around %p
    ",(void*)&shmid);
    
                if((ptr=malloc(MALLOC_SIZE))==NULL)
                    printf("mallc error
    ");
                    printf("malloced from %p to %p
    ",(void*)ptr,(void *)ptr+MALLOC_SIZE);
    
                if((shmid=shmget(IPC_PRIVATE,SHM_SIZE,SHM_MODE))<0)
                    printf("shmat error
    ");
                if((shmptr=shmat(shmid,0,0))==(void*)-1)
                    printf("shmat error
    ");
                printf("shared memory attached from %p to %p
    ",(void *)shmptr,(void*)shmptr+SHM_SIZE);
                if(shmctl(shmid,IPC_RMID,0)<0)
                    printf("shmctl error
    ");
                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
    ");
        }
    
    
        int main(void)
        {
            int fd,i,counter;
            pid_t pid;
            void *area;
    
            if((fd=open("/dev/zero",O_RDWR))<0)
                printf("open error
    ");
            //成功执行时,mmap()返回被映射区的指针
            if((area=mmap(0,SIZE,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
                printf("mmap error
    ");
            close(fd);
    
            TELL_WAIT();
    
            if((pid=fork())<0)
                printf("fork error
    ');
            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,所以对于这种情况,我们只是再次尝试。
    
    技术不分国界
  • 相关阅读:
    __slots__魔法,减少实例属性消耗的内存
    在函数中最好不要用可变类型当参数
    Python的容器模块
    实例和类变量以及类的魔术方法
    推导式
    Python内置函数
    常用的git操作(持续更新)
    h开头的
    e开头的
    如何迁移测试的MAGENTO到正式运行的MAGENTO
  • 原文地址:https://www.cnblogs.com/angels-yaoyao/p/12443610.html
Copyright © 2020-2023  润新知