*inux进程间通信总结(全)

Linux/Unix如何安装一只猫oneko,*inux上有趣的应用,用命令画一只猫儿陪着你。 文章目录安装使用及参数 一个人,一台电脑,一个格子间,注视屏幕,手抚键盘,一天又一天。 很难以想象,这种**的生活。 画一只猫儿陪着我~ 流光岁月,自成一仙,如诗如画。 枯燥的日子里,活跃在屏幕上的小动物成了精神寄托。 话不多说,让我也帮你画一只猫儿吧。 安装 sudo apt-get install oneko 使用及参数 我们直接在命令行中输入: oneko 或者,后台挂起,然后进行... 阅读详情

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

inux驱动设计中,异步通知是一种高效的进程间通信机制 linux驱动设计中,异步通知是一种高效的进程间通信机制 阅读详情

相关推荐

目标检测数据集 第059期-基于yolo标注格式的水果李子检测数据集(含免费分享)

李子作为中国三大水果之一,种植历史超 3000 年,在市场中占据重要地位。传统李子种植、施肥、采摘依赖大量人力,存在资源浪费、产量估算不准、采摘时机难把控等问题。此前,农业领域缺乏针对 “未成熟李子” 的专用数据集,现有水果检测数据集多聚焦于成熟果实(如苹果、柑橘),且未覆盖李子种植的复杂自然场景。在使用该数据集进行学术研究时,应遵守相关的学术规范,引用该数据集的来源,尊重数据集创作者的劳动成果。,用于训练改进型模型,解决李子精准检测的核心痛点。

深度学习算法工程师,专注于前沿算法研究与实战应用。在这里,我将分享深度学习领域的实战经验、优质数据集资源以及最新论文解读与精度分析,旨在与广大技术爱好者共同探索人工智能的奥秘,助力技术成长与创新。 772

信号量 消息队列 共享内存

<br />/****************************************************SEMGET函数*******************************************************************************/<br />可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集: 系统调用:semget();<br /> 原型:intsemget(key_t key,int nsems,i

fay8048的博客 2624

vmware虚拟机网络连接问题

主机:ubuntu14.04 Vitual Network Editor

入门虽易,精通不易,且学且珍惜! 1354

Posix信号量

 Posix信号量分为下面两种: 1.有名信号量 2.无名信号量(基于内存的信号量) 两者使用的函数: ========================================================= 1.sem_open函数,创建或打开一个有名信号量(创建成功后,会在/dev/shm/目录下创建一个 "sem.信号量名" 的信号量文件,如:/dev/shm/se

Tall2Dolphin的专栏 761

esp32-C3固件烧录用户手册

温馨提示:1.esp32-C3 BOOT脚为GPIO9,需要从芯片小板中焊接引出2.芯片小板中已经预留了所有烧录相关的引脚,大家可以按需焊接引线3.程序下载完,使用串口查看log时,需要使用220v供电使用(请务必准守用电安全)。原因:由于插座中包含继电器,计量计等耗电设备,所以usb转串口提供的3.3V电压和电流可能会出现供电不足,表现为usb转串口与电脑连接断断续续,导致无法查看log。

hbwang的博客 1万+

高光谱知识(1)-高光谱成像技术的理解

系列文章目录 高光谱技术可以获得待观测目标或场景的连续单色光谱图像,并通过空间维(x, y)数据和光谱维(λ)数据共同组成三维观测数据立方体,从而为研究人员提供探测目标或场景中的每一个点的空间和光谱特征细节。这种兼具相机的高分辨成像能力和光谱仪的高光谱分辨能力使得高光谱被广泛应用于测绘遥感、目标识别、环境监测与评估、临床影像诊断、过程监控等任务中,并在地理、空间、海洋、气候、大气、农业、植被、生态、医药、安防、制造与色度等多个领域中发挥巨大作用。 文章目录系列文章目录前言一、按获取数据的过程划分二、按照光

Echo_Code的博客 1万+

hp Unix 上 union semun编译不过的问题解决

Linux环境进程间通信(四)信号灯http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/ 今天对文中的例子进行了编译,发现 union   semun编译不过,经过调查发现sem.h中竟然把声明给注释掉了。所以需要自己追加对union   semun 定义。union   semun       {          

jiantiantian的专栏 4590

cellranger安装以及使用

接受cellranger count的输出数据,将同一组的不同测序样本的表达矩阵整合在一起,比如tumor组原来有4个样本,PBMC组有两个样本,现在可以使用aggr生成最后的tumor和PBMC两个矩阵,并且进行标准化去掉测序深度的影响。但是由于病毒基因组太小了,且注释结果只有CDS没有exon,所以曲线救国,将CDS整体替换为exon,也不进行过滤了,直接下一步。虽然安装完毕,但是现在要运行cellranger的话需要在终端输入cellranger的整个路径。自动执行这个命令,需要将“

m0_59974475的博客 9393

Linux应用:进程间通信

SystemV IPC 是 UNIX System V 操作系统中引入的一组进程间通信机制,包括消息队列、共享内存和信号量。这些机制在 Linux 系统中也得到了广泛应用。​。

qq_43441284的博客 1082

Android 16强制横竖屏设置

但开发者端的响应却相对滞后,特别是中小型开发团队受限于大屏设备市场渗透率(截至2023年Q2,Android平板全球激活量约1.2亿台,不足手机设备5%)及开发成本考量,适配优先级普遍偏低。这种适配滞后已形成恶性循环——开发者因设备基数不足降低适配意愿,而用户体验缺陷又反向抑制大屏设备的市场接受度。要破解这一困局,既需要Google持续优化Jetpack Compose等现代化UI工具链,更需要建立开发者激励体系,通过应用商店流量倾斜、开发补贴等经济手段提升适配投入产出比。再不肯好好适配大屏设备?

xiangzhihong8的专栏 978

点云处理指南介绍

指南目录,Open3D与PCL内容介绍

NNNNNathan的博客 1万+

简易 inux shell 编辑器

#include #include #include #include int word_count=0; void type_prompt(){ char name[50],direct[50]; struct passwd *my_info; my_info = getpwuid(getuid()); gethostname(name,sizeof(na

奋斗青年的专栏 596

路径跟踪 | 图解纯追踪算法Pure Pursuit(附ROS C++/Python/Matlab仿真)

纯追踪算法(Pure Pursuit)参考了人类驾驶行为,其基本思想是在待跟踪路径上设置预瞄点(goal-ahead),通过简单的几何方法驱动机器人跟踪预瞄点直至抵达目标位置.本文详细推导纯追踪算法的理论原理及其变种——自适应纯追踪算法(APP)和规范化纯追踪算法(RPP),并提供ROS C++/Python/Matlab三种仿真实现加深理解

FRIGIDWINTER的博客 8408

inux进程描述符—task_struct结构(转)

l linux进程描述符—task_struct结构 为了管理进程,操作系统必须对每个进程所做的事情进行清楚地描述,为此,操作系统使用数据结构来代表处理不同的实体,这个数据结构就是通常所说的进程描述 符或进程控制块,在linux系统中,这就是task_struct结构,在include\linux\sched.h文件中定义。每个进程都会被分配 一个task_struct结构...

126

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Linux进程间通信---管道-----深度解析

文章目录站在文件描述符的角度来理解管道匿名管道管道读写规则匿名管道特点命名管道匿名管道与命名管道的区别用命名管道实现server&client通信 要让两个进程实现通信,必须先让这两个进程看到同一份资源。 站在文件描述符的角度来理解管道 匿名管道 #include <unistd.h> 功能:创建一无名管道 原型 int pipe(int fd[2]); 参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0,失败返回错误代码 管道读写规则

du1232的博客 238

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

inux下检测U盘插入并读取文件

#include "includes.h" #include #include #include "recorder.h" #include #include #include #include #include #include #include #include #include #include #include

AS520213211314的专栏 626

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【linux高级程序设计】(第十一章)System V进程间通信 1

System V, 曾经也被称为 AT&amp;T System V,是Unix操作系统众多版本中的一支。 传统上,System V 被看作是两种UNIX"风味"之一(另一个是 BSD)。然而,随着一些并不基于这两者代码的UNIX实现的出现,例如 Linux 和 QNX, 这一归纳不再准确.   System V IPC基础 System V 的IPC机制主要有消息队列、信号量和共享内存3...

weixin_34309543的博客 96

【Linux】—— 进程的通信之system V

前面讲述了进程间通信的两种方式 匿名管道 和 命名管道 今天我们继续来看看进程间通信的其他方式system V system V共享内存 共享内存,听名字就知道他是一块内存,这块内存一定是可以被俩个进程同时看到的临界资源,正如我们所想的,这块内存被开辟在物理内存上,然后通过页表映射到各个进程的虚拟地址空间的共享内存段,就像我们下图中所画的那样。 共享内存是system V进程间通信速度最快的 ...

chenxiyuehh的博客 700

Linux管道通信方式

进程间通信(IPC:Inner Proceeding Communication) 进程是操作系统实现程序独占系统运行的假象的方法,每个进程都是一个独立的资源管理单元,任何一个进程的全局变量在另一个进程中都是无法看到的,因此各个进程之间是不能够直接的访问对方进程的资源的,不同的进程之间进行信息交互需要通过内核,进程A在内核中开辟一个缓冲区,并且把数据拷贝到此缓冲区中,进程B通过访问内核中的这

_浊酒黑猫 531

Linux管道通信

无名管道和有名管道 先说一下有名管道和无名管道用的函数: 无名管道使用的是 pipe() 有名管道使用的是fifo() 无名管道主要用于有血缘关系的两个进程间通信,是内核使用环形队列机制实现,借助内核缓冲区实现的。有名管道主要用于两个不相干的进程间通信,我认为之所以叫有名管道是因为他们借助mkfifo()函数创建的伪文件利用内核缓冲区进行通信,因为创建文件可以指定文件名所以操作和使用文件几乎一样。 首先关于无名管道 pipe()函数 需要指定两个文件描述符,通过pipe()函数创建一个管道使其一端读文件一端

ZR 557

共享内存的实现---Linux内核笔记

系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shmid_kernel结构联系起来的)。struct shmid_kernel /* private to the kernel */{  struct kern_ipc_perm shm_perm;

阿木的专栏 2294

linux下两个进程以相同的键值创建共享内存,得到地址不同,Linux编程练习 --SystemV共享内存...

1.共享内存段的基本概念共享内存就是由几个进程共享一段内存区域,可以说是IPC最快形式,因为它无须任何中间操作,它只是把内存段直接映射到调用进程的地址空间中。共享内存段通信的使用方式和消息通信是类似的。首先用shmget系统调用为进程创建一个共享内存段,当需要该共享内存时,利用系统调用shmat将申请的共享内存段映射到进程的私有空间中,之后进程可以用与访问其他虚拟地址空间相同的方法对它进行访问了。...

weixin_30591519的博客 717

常用的Linux系统调用命令

常用的Linux系统调用命令  下面一些函数已经过时,被新的更好的函数所代替了(gcc在链接这些函数时会发出警告),但因为兼容的原因还保留着,这些函数将在前面标上“*”号以示区别。 一、进程控制 fork 创建一个新进程 clone 按指定条件创建子进程 execve 运行可执行文件 exit 中止进程 _exit 立即中止当前进程 getdtables...

491

Linux系统编程(三)

IPC(进程间通信) 文件类型: - 文件 d 目录 l 符号链接 伪文件: s 套接字 b 块设备 c 字符设备 D 管道管道:

m0_37533985的博客 323

linux调试工具ipcs的深入分析

1)system v系统共享内存 用ipcs调试共享内存 测试源程序如下: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <...

weixin_30478619的博客 125
上一篇: 设计模式——单例模式Singleton
下一篇: 内存映射文件File Mapping
Scutware
博客等级 码龄14年 4粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值