inux进程间通信总结(全)

IPC进程间通信(Inter-Process Communication)就是指多个进程之间相互通信,交换信息的方法。Linux IPC基本上都是从Unix平台上继承而来的。主要包括最初的Unix IPC,System V IPC以及基于Socket的IPC。另外,Linux也支持POSIX IPC。

System V,BSD,POSIX

    System V是Unix操作系统最早的商业发行版之一。它最初由AT&T(American Telephone & Telegraph)开发,最早在1983年发布。System V主要发行了4个版本,其中SVR4(System V Release 4)是最成功的版本。BSD(Berkeley Software Distribution,有时也被称为Berkeley Unix)是加州大学于1977至1995年间开发的。在19世纪八十年代至九十年代之间,System V和BSD代表了Unix的两种主要的操作风格。它们的主要区别如下:

    系统                      System V           BSD
    root脚本位置            /etc/init.d/       /etc/rc.d/
    默认shell                 Bshell             Cshell
    文件系统数据            /etc/mnttab     /etc/mtab
    内核位置                  /UNIX             /vmUnix
    打印机设备                lp                  rlp
    字符串函数                memcopy       bcopy
    终端初始化设置文件    /etc/initab       /etc/ttys
    终端控制                  termio            termios

    Linux系统的操作风格往往介于这两种风格之间。

    POSIX(Portable Operating System Interface [for Unix])是由IEEE(Institute of Electrical and Electronics Engineers,电子电气工程协会)开发的。现有的大部分Unix都遵循POSIX标准,而Linux从一开始就遵循POSIX标准。

最初的Unix IPC

1、信号

    信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执行任何操作来等待信号的到达。信号异步通知接收信号的进程发生了某个事件,然后操作系统将会中断接收到信号的进程的执行,转而去执行相应的信号处理程序。

    (1)注册信号处理函数
        #include <signal.h>
        /*typedef void (*sighandler_t)(int);  sighandler_t signal(int signum,sighandler_t handler);*/
        * void (*signal(int signum, void (*handler)(int)))(int);  //SIG_IGN && SIG_DFL
        * int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

    (2)发送信号
        #include <signal.h>
        * int kill(pid_t pid,int sig); //#include <sys/types.h> 
        * int raise(int sig);            //kill(getpid(),sig);
        * unsigned int alarm(unsigned int seconds); //(#include <unistd.h>) seconds秒后,向进程本身发送SIGALRM信号。

    (3)信号集
        信号集被定义为:typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
        * int sigaddset(sigset_t *set,int sig);
        * int sigemptyset(sigset_t *set);

2、管道(Pipe)

    管道用来连接不同进程之间的数据流。

    (1)在两个程序之间传递数据的最简单的方法是使用popen()和pclose()函数:
        #include <stdio.h>
        FILE *popen(const char *command, const char *open_mode);
        int pclose(FILE *stream);
    popen()函数首先调用一个shell,然后把command作为参数传递给shell。这样每次调用popen()函数都需要启动两个进程;但是由于在Linux中,所有的参数扩展(parameter expansion)都是由shell执行的,这样command中包含的所有参数扩展都可以在command程序启动之前完成。

    (2)pipe()函数:
        #include <unistd.h>
        int pipe(int pipefd[2]);
    popen()函数只能返回一个管道描述符,并且返回的是文件流(file stream),可以使用函数fread()和fwrite()来访问。pipe()函数可以返回两个管道描述符:pipefd[0]pipefd[1],任何写入pipefd[1]的数据都可以从pipefd[0]读回;pipe()函数返回的是文件描述符(file descriptor),因此只能使用底层的read()和write()系统调用来访问。pipe()函数通常用来实现父子进程之间的通信。

    (3)命名管道:FIFO
        #include <sys/types.h>
        #include <sys/stat.h>
        int mkfifo(const char *fifo_name, mode_t mode);
    前面两种管道只能用在相关的程序之间,使用命名管道可以解决这个问题。在使用open()打开FIFO时,mode中不能包含O_RDWR。mode最常用的是O_RDONLY,O_WRONLY与O_NONBLOCK的组合。O_NONBLOCK影响了read()和write()在FIFO上的执行方式。

    PS:要想查看库函数用法,最可靠的资料来自Linux manual page:

    $sudo apt-get install manpages-dev

    $man 3 function_name

 

System V IPC

    System V IPC指的是AT&T在System V.2发行版中引入的三种进程间通信工具:(1)信号量,用来管理对共享资源的访问 (2)共享内存,用来高效地实现进程间的数据共享 (3)消息队列,用来实现进程间数据的传递。我们把这三种工具统称为System V IPC的对象,每个对象都具有一个唯一的IPC标识符(identifier)。要保证不同的进程能够获取同一个IPC对象,必须提供一个IPC关键字(IPC key),内核负责把IPC关键字转换成IPC标识符。   

    System V IPC具有相似的语法,一般操作如下:

    (1)选择IPC关键字,可以使用如下三种方式:

       a)IPC_PRIVATE。由内核负责选择一个关键字然后生成一个IPC对象并把IPC标识符直接传递给另一个进程。
       b)直接选择一个关键字。
       c)使用ftok()函数生成一个关键字。

    (2)使用semget()/shmget()/msgget()函数根据IPC关键字key和一个标志flag创建或访问IPC对象。如果key是IPC_PRIVATE;或者key尚未与已经存在的IPC对象相关联且flag中包含IPC_CREAT标志,那么就会创建一个全新的IPC对象。

    (3)使用semctl()/shmctl()/msgctl()函数修改IPC对象的属性。

    (4)使用semctl()/shmctl()/msgctl()函数和IPC_RMID标志销毁IPC实例。

    System V IPC为每个IPC对象设置了一个ipc_perm结构体并在创建IPC对象的时候进行初始化。这个结构体中定义了IPC对象的访问权限和所有者:

    struct ipc_perm{
       uid_t uid;   //所有者的用户id
       gid_t gid;   //所有者的组id
       uid_t cuid;  //创建者的用户id
       gid_t cgid;  //创建者的组id
       mode_t mode; //访问模式
       …
    };

    shell中管理IPC对象的命令是ipcs、ipcmk和ipcrm。

1、信号量(Semaphores)

    System V的信号量集表示的是一个或多个信号量的集合。内核为每个信号量集维护一个semid_ds数据结构,而信号量集中的每个信号量使用一个无名结构体表示,这个结构体至少包含以下成员:
    struct{
        unsigned short semval;//信号量值,总是>=0
        pid_t sempid;  //上一次操作的pid
       …
    };

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    (1)创建或访问信号量
        * int semget(key_t key,int nsems,int flag); 
    nsems指定信号量集中信号量的个数,如果只是获取信号量集的标识符(而非新建),那么nsems可以为0。flag的低9位作为信号量的访问权限位,类似于文件的访问权限;如果flag中同时指定了IPC_CREAT和IPC_EXCL,那么如果key已与现存IPC对象想关联的话,函数将会返回EEXIST错误。例如,flag可以为IPC_CREAT|0666。

    (2)控制信号量集
        * int semctl(int semid,int semnum,int cmd,union semun arg);
    对semid信号量集合执行cmd操作;cmd常用的两个值是:SETVAL初始化第semnum个信号量的值为arg.val;IPC_RMID删除信号量。

    (3)对一个或多个信号量进行操作
        * int semop(int semid,struct sembuf *sops,unsigned nsops);
        * struct sembuf{
              unsigned short sem_num;  //信号量索引
              short   sem_op;     //对信号量进行的操作,常用的两个值为-1和+1,分别代表P、V操作
              short   sem_flag;   //比较重要的值是SEM_UNDO:当进程结束时,相应的操作将被取消;同时,如果进程结束时没有释放资源的话,系统会自动释放
           };

2、共享内存

    共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种IPC。

    #include <sys/ipc.h>
    #include <sys/shm.h>
    (1)创建或访问共享内存
        * int shmget(key_t key,size_t size,int shmflg);

    (2)附加共享内存到进程的地址空间
        * void *shmat(int shmid,const void *shmaddr,int shmflg);//shmaddr通常为NULL,由系统选择共享内存附加的地址;shmflg可以为SHM_RDONLY

    (3)从进程的地址空间分离共享内存
        * int shmdt(const void *shmaddr); //shmaddr是shmat()函数的返回值

    (4)控制共享内存
        * int shmctl(int shmid,int cmd,struct shmid_ds *buf);
        * struct shmid_ds{
              struct ipc_perm shm_perm;
              …
          }; 
    cmd的常用取值有:(a)IPC_STAT获取当前共享内存的shmid_ds结构并保存在buf中(2)IPC_SET使用buf中的值设置当前共享内存的shmid_ds结构(3)IPC_RMID删除当前共享内存

3、消息队列

    消息队列保存在内核中,是一个由消息组成的链表。

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    (1)创建或访问消息队列
    * int msgget(key_t key,int msgflg);

    (2)操作消息队列
        * int msgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);
    msg指向的结构体必须以一个long int成员开头,作为msgrcv()的消息类型,必须大于0。nbytes指的是msg指向结构体的大小,但不包括long int部分的大小
        * ssize_t msgrcv(int msqid,void *msg,size_t nbytes,long msgtype,int msgflg);
    如果msgtype是0,就返回消息队列中的第一个消息;如果是正整数,就返回队列中的第一个该类型的消息;如果是负数,就返回队列中具有最小值的第一个消息,并且该最小值要小于等于msgtype的绝对值。

    (3)控制消息队列
        * int msgctl(int msqid,int cmd,struct msqid_ds *buf);
        * struct msqid_ds{
              struct ipc_perm msg_perm;
              …
           };

Socket
  套接字(Socket)是由Berkeley在BSD系统中引入的一种基于连接的IPC,是对网络接口(硬件)和网络协议(软件)的抽象。它既解决了无名管道只能在相关进程间单向通信的问题,又解决了网络上不同主机之间无法通信的问题。

  套接字有三个属性:域(domain)、类型(type)和协议(protocol),对应于不同的域,套接字还有一个地址(address)来作为它的名字。

  域(domain)指定了套接字通信所用到的协议族,最常用的域是AF_INET,代表网络套接字,底层协议是IP协议。对于网络套接字,由于服务器端有可能会提供多种服务,客户端需要使用IP端口号来指定特定的服务。AF_UNIX代表本地套接字,使用Unix/Linux文件系统实现。

  IP协议提供了两种通信手段:流(streams)和数据报(datagrams),对应的套接字类型(type)分别为流式套接字和数据报套接字。流式套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字使用TCP协议。数据报套接字(SOCK_DGRAM)提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议。

  一种类型的套接字可能可以使用多于一种的协议来实现,套接字的协议(protocol)属性用于指定一种特定的协议。

 

总结:

 

 

 

System V IPC API

 

1,消息队列

int ftok(const char *pathname, int prj_id);

int msgget(key_t key,int msgflag);

int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);

int msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);

 

2,信号量

int semget(key_t key,int nsems,int semflag);

int semctl(int semid,int semnum,int cmd,…);

int semop(int semid,struct sembuf *sops,unsigned nsops,struct timespec *timeout);

 

3,共享内存

int shmget(key_t key,size_t size,int shmflag);

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

  

POSIX IPC API


Unix/Linux下的IPC---信号量集  

最初的Unix IPC包括:管道、FIFO、信号;
System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;
POSIX IPC包括:POSIX消息队列、POSIX信号灯、POSIX共享内存区;
由于Unix系统版本的多样性,电子电气工程协会(IEEE)开发了一套独立的Unix标准,这套新的ANSI Unix标准被称为计算机环境的可移植操作系统接口(POSIX:Portable Operation System Interface).现有大部分Unix和流行的版本都遵循POSIX标准;而Linux从一开始就遵循了POSIX标准;

System V IPC对象属于系统内核对象,执行同步操作的时候,系统内核参与完成了大部分操作,所以它是一套重量级同步对象;
POSIX IPC对象属于用户进程对象,执行同步操作的时候,不需要系统内核参与,所以它是一套轻量级同步对象;
一般来说,使用POSIX IPC来控制对共享资源的访问,就足够了;

典型的IPC对象:管道、命名管道、信号、信号灯、信号量集、消息队列、共享内存;下面仅介绍信号量集、共享内存;

一、信号量集:
POSIX IPC标准对信号量的的要求并不高:信号量(sem_init)、命名信号量(sem_open);
System V IPC要求信号量必须是一个集合,即:信号量集;
信号量集和信号量一样,都是为了控制多个进程对共享资源的同步访问而引入的同步对象;System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控制的需要;

1、信号量集的创建与打开:
   系统调用semget()用于创建一个新的信号量集,或者是存取一个已经存在的信号量集;
   函数原型: int semget(key_t key, int nsems, int semflag);
   参数说明: key     --> 需要创建或打开的信号量集的键,用于唯一地标记一个信号量集;这个参数是用户程序可以直接访问的用户态参数;
             nsems   --> 表示待创建的信号量集key中的信号量的个数,这个参数只在创建信号量集的时候有效;
             semflag --> 表示调用函数的操作类型,也可以用于设置信号量集的访问权限,两者通过逻辑或(or)表示;
   返回值:成功:返回一个正数,这个正数也用于唯一地标记已经创建或打开的信号量集,这个唯一标示由系统内核使用;这个正数被称为是IPC标识符;
          失败:返回-1;并设置错误码errno来标记错误原因:
               EEXIST(信号量集已经存在,无法创建)
               EIDRM(信号量集已经删除)
               ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
               ENOMEM(没有足够的内存创建新的信号量集)
               ENOSPC(超出限制)
   当调用semget()创建一个信号量集的时候,信号量集的semid_ds结构会被初始化;ipc_perm中的各个量被设置为相应的值;sem_nsems被设置为参数nsems的值;sem_otime被设置为0,sem_ctime被设置为当前时间;
   系统调用semget()的第一个参数是关键字值(一般由系统调用ftok()返回的).系统内核将此值和系统中存在的其它的信号量集的关键字值进行比较,不存在的话,则直接创建这个新的信号量集,如果已经存在同关键字值的信号量集,则直接打开这个信号量集,不必再新建信号量集;打开和存取操作与参数semflag有关.IPC_CREAT:如果待创建的信号量集在系统内核中不存在,则创建信号量集;IPC_EXCL与IPC_CREAT同时使用时,如果信号量集在系统内核中已经存在,则调用失败;如果单独使用IPC_CREAT,则系统调用semget()要么返回新创建的信号量集的标识符,要么返回系统内核中已经存在的具有相同关键字值的信号量集的标识符;如果IPC_EXCL与IPC_CREAT一起使用,则要么返回新创建的信号量集的标识符,要么返回-1;IPC_EXCEL单独使用时没有意义;
   参数nsems指出了一个新创建的信号量集中应该创建的信号量的个数;

2、信号量相关的内核结构:
   struct semid_ds
   {
     struct ipc_perm   sem_perm;       /* operation permission struct */
     struct sem*       sem_base;       /* ptr to first semaphore in set:指向数组中第一个信号量的指针*/
     ushort_t          sem_nsems;      /* number of semaphores in set:信号量集(数组)中的信号量的个数 */
     #if defined(_LP64)
       time_t          sem_otime;      /* last semop time:最后一次semop()操作的时间 */
       time_t          sem_ctime;      /* last change time:最后一次改动此数据结构的时间 */
     #else   /* _LP64 */
       time_t          sem_otime;      /* last semop time */
       int32_t         sem_pad1;       /* reserved for time_t expansion */
       time_t          sem_ctime;      /* last change time */
       int32_t         sem_pad2;       /* time_t expansion */
     #endif  /* _LP64 */
     int               sem_binary;     /* flag indicating semaphore type */
     long              sem_pad3[3];    /* reserve area */
    };
    
    /*Common IPC access structure*/
    struct ipc_perm
    {
      uid_t           uid;    /* owner's user id */
      gid_t           gid;    /* owner's group id */
      uid_t           cuid;   /* creator's user id */
      gid_t           cgid;   /* creator's group id */
      mode_t          mode;   /* access modes */
      uint_t          seq;    /* slot usage sequence number */
      key_t           key;    /* key */
      #if !defined(_LP64)
        int           pad[4]; /* reserve area */
      #endif
    };
    mode的取值:
    权限     位
    用户读 : 0400
    用户写 : 0200
    组读   : 0040
    组写   : 0020
    其他读 : 0004
    其他写 : 0002
    内核中的sem结构:
    在数据结构semid_ds中包含一个指向信号量数组的指针;此数组中的每一个元素都是一个数据结构sem.它定义在linux/sem.h文件中:
    /*One semaphore structure for each semaphore in the system.*/
    struct sem
    {
      short   sempid;   /*pid of last operation:最后一个操作的PID(进程ID)*/
      ushort  semval;   /*current value:信号量的当前值*/
      ushort  semncnt;  /*num procs awaiting increase in semval:等待资源的进程数量*/
      ushort  semzcnt;  /*num procs awaiting semval=0:等待资源完全空闲的进程数量*/
    };

3、PV操作:
   P操作(代表荷兰语Proberen:尝试):
      等待一个信号灯,等待申请使用一个单位的共享资源 ,该操作测试这个信号灯的值,如果小于或等于0,则阻塞调用者,一旦值变大,就将它减1;
      
   V操作(代表荷兰语Verhogen:增加):
      挂出(post)一个信号灯,该操作将信号灯的值加1;表示释放一个单位的共享资源;
      
4、信号量集的操作:
   系统调用semop()对信号量集进行原子操作;
   函数原型:int semop(int semid, struct sembuf* semoparray, size_t nsops);
   参数说明:semid      --> 信号量集的IPC标识符,用于引用对应的信号量集;
            semoparray --> sembuf结构的数组,用于指定调用semop()函数所做的操作(PV操作);
            nsops      --> 指出数组semoparray中操作结构元素的个数:有nsops个sembuf元素参与了PV操作;nsops>=1;
   返回值:==0:成功; -1:失败;
   
   函数说明:semoparray是一个sembuf结构的数组,其中每个元素都代表一个操作,由于此函数执行的是原子操作,所以,一旦执行,就会执行数组中所有sembuf元素所定义的操作;
   当函数返回失败时,errno:
   E2BIG(nsops大于最大的ops数目),即:一次对信号的操作数超出系统的限制;
   EACCESS(权限不够),即:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能;
   EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行),即:信号操作暂时不能满足,需要重试;
   EFAULT(sops指向的地址无效),即:sops或timeout指针指向的空间不可访问;
   EFBIG:sem_num指定的值无效;
   EIDRM(信号量集已经删除)
   EINTR(当睡眠时接收到其他信号),即:系统调用阻塞时,被信号中断;
   EINVAL(信号量集不存在,或者semid无效)
   ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
   ERANGE(信号量值超出范围),即:信号所允许的值越界;
   struct sembuf结构:
   struct sembuf
   {
     ushort_t        sem_num;        /* semaphore index in array:待操作的信号量集中的某一个信号量的索引 */
     short           sem_op;         /* semaphore operatio:对该信号量所执行的操作 */
     short           sem_flg;        /* operation flags:操作标志 */
   };
   成员sem_num:待操作的信号量集中的某一个信号量的索引,所以,其取值范围是[0,信号量集中信号量的个数);
               即:操作信号量在信号集中的编号,第一个信号的编号是0;
   成员sem_op:定义了semop()函数对信号量所作的操作;如果其值为正数,该值会加到现有的信号内含值中.通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值.通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0.
   成员sem_flg:定义了semop()的操作标志;可能的选择有两种:
               IPC_NOWAIT --> 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息.
               IPC_UNDO   --> 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值.这样做的目的在于避免程序在异常情况下结束时未将锁定的资源
                              解锁,造成该资源永远锁定.
   sem_op成员的取值详解:
   >0:释放相应数量的资源,将sem_op的值累加到信号量的值上;
   =0:进程阻塞,直到相应信号量的值为0,当信号量的值已经为0,函数立即返回;如果信号量的值不为0,则依据sem_flag的IPC_NOWAIT位决定函数操作;如果sem_flag指定了IPC_NOWAIT位,则semop函数出错返回EAGAIN(异步返回).如果sem_flag没有指定IPC_NOWAIT,则将该信号量的semncnt值加1(等待空闲资源的进程数多一个),然后挂起进程,直到下述情况发生:信号量的值为0,将信号量的semzcnt的值减1(等待资源完全空闲的进程数少一个),函数semop成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数semop出错返回EIDRM;进程捕捉到系统信号,并从信号处理函数返回,在此情况下,将此信号量的semncnt值减1(等待资源完全空闲的进程数少一个),函数semop出错返回EINTR;
   <0:请求sem_op的绝对值所表示的数目的资源;如果相应的资源数可以满足请求,则信号量的值减去sem_op的绝对值,函数成功返回;当相应的资源数目不能满足请求时,这个操作与sem_flag有关;如果sem_flag指定了IPC_NOWAIT,则semop函数出错返回EAGAIN(异步返回);如果sem_flag没有指定IPC_NOWAIT,则将该信号量的semncnt值加1(表示等待空闲资源的进程多了一个),然后挂起进程,直到下述情况发生:当相应的资源数目可以满足请求时,该信号量的值减去sem_op的绝对值(表示|sem_op|个数目的共享资源被申请走了),然后成功返回;当此信号量已被删除(只有超级用户或创建用户进程拥有此权限)时,函数出错返回EIDRM;当进程捕捉到系统信号并从信号处理函数返回时,将此信号量的semzcnt值减1,函数semop出错返回EINTR;
   简单地说:如果sem_op为负数,则把信号量的值减去sem_op的绝对值,此时,sem_op的绝对值表示本次调用semop()操作需要申请的共享资源的数量;这与信号量集控制的资源有关;如果没有设置IPC_NOWAIT,那么,调用进程将进入休眠状态,直到信号量控制的资源具有可用的数量为止;如果sem_op为正数,则信号量的值加上sem_op的绝对值,表示调用进程需要归还|sem_op|个单位数目的共享资源给系统,即:调用进程释放信号量集所控制的共享资源;如果sem_op为0,那么,调用进程将调用sleep()休眠,直到信号量的值为0;这在一个调用进程等待完全空闲的共享资源时使用;
   
5、信号量集的控制:
   系统调用semctl()可以实现对信号量集的控制;
   函数原型:int semctl(int semid, int semnum, int cmd, /*union semun arg*/...);
   参数说明:semid  --> 信号量集的IPC标识符;
            semnum --> 信号量集semid中的某一个信号量的索引;
            cmd    --> 对信号量集semid中的特定信号量semnum执行的操作命令;
            变参   --> 这是一个联合体类型union semun的副本,而不是一个指向联合类型的指针;联合体中各个量的使用情况与参数cmd的设置有关;
   返回值:-1:失败;
          >=0:成功(依赖cmd参数的设置):
              GETVAL          the value of semval
              GETPID          the value of (int) sempid
              GETNCNT         the value of semncnt
              GETZCNT         the value of semzcnt
              cmd取其余值时,返回0表示成功;
   union semun
   {
     int              val;    /* value for SETVAL */
     struct semid_ds* buf;    /* buffer for IPC_STAT&IPC_SET */
     unsigned short*  array;  /* array for GETALL&SETALL */
     structseminfo*   __buf;  /* buffer for IPC_INFO */
     void*__pad;
   } arg;
   cmd参数的取值详解:
   CMD的取值    操作描述
   GETVAL       返回成员semnum的semval值,信号量集中的一个单个的信号量的值;
   SETVAL       使用arg.val对该信号量的semnum.sempid赋值(需要参数arg),设置信号量集中的一个单独的信号量的值;
   GETPID       返回成员semnum的sempid值,最后一个执行semop操作的进程的PID;
   GETNCNT      返回成员semnum的semncnt值,正在等待资源的进程数目;
   GETZCNT      返回成员semnum的semzcnt值,正在等待完全空闲的资源的进程数目;
   GETALL       将该信号量集中所有信号量的值赋值到arg.array(需要参数arg)所指向的数组中,用于读取信号量集中的所有信号量的值;
   SETALL       使用arg.array所指向的数组中的值对信号量集赋值(需要参数arg),设置信号量集中的所有的信号量的值;
   IPC_RMID     删除信号量集.此操作只能由具有超级用户的进程或信号量集拥有者的进程执行,
                这个操作会影响到正在使用该信号量集的进程;
   IPC_SET      设置此信号量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值.值来自semun.buf结构中
                此操作只能由具有超级用户的进程或信号量集拥有者的进程执行;
                设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数;
   IPC_STAT     读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中;


《linux的POSIX IPC用到的工具》

POSIX IPC为每个对象都使用文件描述符,这与System V的IPC不同,System V的每个对象都使用键值(keys).
由于每个IPC对象可以写到一个纯文本文件,因此,在纯文本文件上使用的工具对于操作POSIX IPC对象来说通常已经足够了.


1)POSIX共享内存
在Linux中,POSIX共享内存对象驻留在tmpfs伪文件系统中.系统默认挂载在/dev/shm目录下.
当调用shm_open函数创建或打开POSIX共享内存对象时,系统会将创建/打开的共享内存文件放到/dev/shm目录下.

同样也可以手工在/dev/shm目录下创建文件,以供POSIX共享内存程序连接使用.

我们用下面的源程序对POSIX共享内存进行测试,如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>
void error_out(const char *msg)
{
        perror(msg);
        exit(EXIT_FAILURE);
}

int main (int argc, char *argv[])
{
        int r;
        const char *memname = "/mymem";
        const size_t region_size = sysconf(_SC_PAGE_SIZE);
        int fd = shm_open(memname, O_CREAT|O_TRUNC|O_RDWR, 0666);
        if (fd == -1)
                error_out("shm_open");
        r = ftruncate(fd, region_size);
        if (r != 0)
                error_out("ftruncate");
        void *ptr = mmap(0, region_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (ptr == MAP_FAILED)
                error_out("MMAP");
        close(fd);
        pid_t pid = fork();
        if (pid == 0){
                u_long *d = (u_long *)ptr;
                *d = 0xdeadbeef;
                exit(0);
        }
        else{
                int status;
                waitpid(pid, &status, 0);
                printf("child wrote %#lx\n", *(u_long *)ptr);
        }
        sleep(50);
        r = munmap(ptr, region_size);
        if (r != 0)
                error_out("munmap");
        r = shm_unlink(memname);
        if (r != 0)
                error_out("shm_unlink");
        return 0;
}

编译程序pmem.c
gcc pmem.c -o pmem -lrt


执行程序pmem
./pmem
child wrote 0xdeadbeef

注:程序会通过shm_open函数创建一个共享内存对象,并通过mmap函数将文件映射到内存,此时修改内存,即是修改文件.
子进程向共享内存写数据,父进程将共享内存数据打印输出,在输出完成后,会等待50秒钟.我们可以在另一个终端看到/dev/shm/mymem文件,如下:

ls -l /dev/shm/mymem 
-rw-r--r-- 1 root root 4096 2011-03-16 15:22 /dev/shm/mymem



2)POSIX消息队列

在Linux中通过mqueue伪文件系统来显示POSIX消息队列,与POSIX共享内存不同的是,它没有一个标准的挂载点来为这个文件系统服务.
如果需要调试来自shell的POSIX消息队列,则需要手动挂载文件系统,例如,为了将其挂载在命名为/mnt/mqs的目录下,可以使用以下命令:

mkdir /mnt/mqs
mount -t mqueue none /mnt/mqs


我们用下面的源程序对POSIX消息队列进行测试,如下:

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

struct message{
 char mtext[128];
};

int send_msg(int qid, int pri, const char text[])
{
 int r = mq_send(qid, text, strlen(text) + 1,pri);
 if (r == -1){
  perror("mq_send");
 }
 return r;
}

void producer(mqd_t qid)
{
 send_msg(qid, 1, "This is my first message.");
 send_msg(qid, 1, "This is my second message.");

 send_msg(qid, 3, "No more messages.");
}

void consumer(mqd_t qid)
{
 struct mq_attr mattr;
 do{
  u_int pri;
  struct message msg;
  ssize_t len;

  len = mq_receive(qid, (char *)&msg, sizeof(msg), &pri);
  if (len == -1){
   perror("mq_receive");
   break;
  }
  printf("got pri %d '%s' len=%d\n", pri, msg.mtext, len);

  int r = mq_getattr(qid, &mattr);
  if (r == -1){
   perror("mq_getattr");
   break;
  }
 }while(mattr.mq_curmsgs);
}

int
main (int argc, char *argv[])
{
 struct mq_attr mattr = {
  .mq_maxmsg = 10,
  .mq_msgsize = sizeof(struct message)
 };

 mqd_t mqid = mq_open("/myq",
    O_CREAT|O_RDWR,
    S_IREAD|S_IWRITE,
    &mattr);
 if (mqid == (mqd_t) -1){
  perror("mq_open");
  exit (1);
 }

 pid_t pid = fork();
 if (pid == 0){
  producer(mqid);
  mq_close(mqid);
  exit(0);
 }
 else
 {
  int status;
  wait(&status);
  sleep(50);
  consumer(mqid);
  mq_close(mqid);
 }
 mq_unlink("/myq");
 return 0;
}
编译:
gcc posix_mqs.c -o posix_mqs -lrt

运行程序:
./posix_mqs 
got pri 3 'No more messages.' len=18
got pri 1 'This is my first message.' len=26
got pri 1 'This is my second message.' len=27


注:程序通过fork使子进程发送三条消息到消息队列,父进程从消息队列中接收这三条消息.
/mnt/mqs/myq文件是程序创建的消息队列文件,程序会自动在伪文件系统mqueue中创建消息队列文件.

如果我们挂载伪文件系统到多个目录,而在每个挂载目录下都会生成消息队列文件.
例如:
mkdir /dev/mqs/
mount -t mqueue none /dev/mqs

df -aTh
Filesystem    Type    Size  Used Avail Use% Mounted on
/dev/sda1     ext3     19G  3.3G   15G  19% /
proc          proc       0     0     0   -  /proc
sysfs        sysfs       0     0     0   -  /sys
devpts      devpts       0     0     0   -  /dev/pts
tmpfs        tmpfs    512M     0  512M   0% /dev/shm
none   binfmt_misc       0     0     0   -  /proc/sys/fs/binfmt_misc
sunrpc  rpc_pipefs       0     0     0   -  /var/lib/nfs/rpc_pipefs
none        mqueue       0     0     0   -  /mnt/mqs
none        mqueue       0     0     0   -  /dev/mqs

再次运行程序
./posix_mqs

我们看到在两个mqueue伪文件系统挂载的目录下都生成了myq文件
cat /mnt/mqs/myq /dev/mqs/myq 
QSIZE:71         NOTIFY:0     SIGNO:0     NOTIFY_PID:0     
QSIZE:71         NOTIFY:0     SIGNO:0     NOTIFY_PID:0  

QSIZE:71表示/mnt/mqs/myq消息队列中一共有71个字节.
NOTIFY,SIGNO,NOTIFY_PID都和mq_notify函数有关.


3)POSIX信号量

在Linux中,POSIX信号量对象驻留在tmpfs伪文件系统中,同POSIX共享内存一样,有名字的信号量作为文件被创建在/dev/shm里.

我们用下面的源程序来测试POSIX信号量,如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <assert.h>
void busywait(void)
{
 clock_t t1 = times(NULL);
 while(times(NULL) - t1 < 2);
}
int main (int argc, char *argv[])
{
 const char *message = "Hello World\n";
 int n = strlen(message)/2;
 sem_t *sem = sem_open("/thesem", O_CREAT, S_IRUSR|S_IWUSR);
 sleep(30);
 assert(sem != NULL);
 int r = sem_init(sem, 1, 0);
 assert(r == 0);
 pid_t pid = fork();
 int i0 = (pid == 0) ? 0 :n ;
 int i;

 if (pid)
  sem_wait(sem);
 for (i = 0;i < n;i++){
  write (1,message+i0+i,1);
  busywait();
 }
 if (pid == 0)
  sem_post(sem);
}

编译:
gcc posix-sem.c -o posix-sem -lrt

执行posix-sem程序:
./posix-sem 

在另一个终端查看信号量文件,如下:
ls -l /dev/shm/
total 4
-rw------- 1 root root 16 Mar 18 10:32 sem.thesem

一个被命名为thesem的信号量出现在/dev/shm/sem.thesem

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值