linux IPC 转载

来源:互联网 发布:国际劳工组织数据库 编辑:程序博客网 时间:2024/05/21 23:00

linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。Linux则把两者继承了下来,如图示:



其中,

最初Unix IPC包括:管道、FIFO、信号;

System V IPC包括:System V消息队列、System V信号灯、System V共享内存区;

Posix IPC包括: Posix消息队列、Posix信号灯、Posix共享内存区。

 

有两点需要简单说明一下:

1)由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循POSIX标准;

2)BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。

图一给出了linux 所支持的各种IPC手段,在本文接下来的讨论中,为了避免概念上的混淆,在尽可能少提及Unix的各个版本的情况下,所有问题的讨论最终都会归结到Linux环境下的进程间通信上来。并且,对于Linux所支持通信手段的不同实现版本(如对于共享内存来说,有Posix共享内存区以及System V共享内存区两个实现版本),将主要介绍Posix API。

 

linux下进程间通信的几种主要手段简介:

1. 管道(Pipe)及有名管道(named 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

 

2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);

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

    (1)注册信号处理函数
        #include <signal.h>
       
        * 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);

 

   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。)

 

3. 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

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

    #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;
              
           };

 

4.  共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

   共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种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删除当前共享内存

 

5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

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:当进程结束时,相应的操作将被取消;同时,如果进程结束时没有释放资源的话,系统会自动释放
          
 };

 

6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

套接字(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)属性用于指定一种特定的协议。

当客户端程序尝试连接sockfd套接字时,accept返回一个新的套接字与客户端进行通信。如果addr不是NULL,那么客户端的地址将会保存在addr所指向的结构体中;调用accept()前必须先将addrlen初始化为addr所指向结构体的大小,accept()返回以后,addrlen将会被设置成客户端套接字地址结构体的实际大小。然后,通过对accept()返回的套接字执行read()和write()操作即可实现与客户端的简单的通信。

 

 

下面将对上述通信机制做具体阐述。

附1:参考文献[2]中对linux环境下的进程进行了概括说明:

一般来说,linux下的进程包含以下几个关键要素:

  • 有一段可执行程序;
  • 有专用的系统堆栈空间;
  • 内核中有它的控制块(进程控制块),描述进程所占用的资源,这样,进程才能接受内核的调度;
  • 具有独立的存储空间

进程和线程有时候并不完全区分,而往往根据上下文理解其含义。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 简理财不能身份信息确认怎么办 德国领事馆没有收到预约邮件怎么办 父母一方带孩子英国签证怎么办 去韩国自由行签证怎么办 韩国自由行签证的该怎么办 法院离婚判决书没了怎么办 离婚判决书对方没收到怎么办 法院判离怎么办离婚证 判决书下来后没钱还怎么办 拿调解书怎么办离婚证 判决书生效后对方拒不履行怎么办 收到民事判决公告该怎么办 苹果手机gps信号弱怎么办 二审败诉后拿到判决书怎么办 农商行房贷逾期一天怎么办 农商行房贷逾期怎么办 农发行车改司机怎么办 混泥土地泵排量不稳怎么办 改嫁上海老公孩子的户口怎么办 给小孩办社保卡怎么办 有上海居住证积分怎么办医保 未办理居住证积分新生儿医保怎么办 厦门中考居住证没满三年怎么办 海归落户过了两年期怎么办 借呗学历填错了怎么办 上海落户应届生分不够怎么办 应届生落户分数不够72分怎么办? 上海应届生落户时间延误怎么办 南京市区户口签江宁怎么办 深圳公司集体户口离职后怎么办 济南本地户口不符合入学条件怎么办 上海住亲戚家怎么办居住证 政府卖非农户口怎么办 90年代买了户口怎么办 上海应届大学生积分不够怎么办 广州居住证回执单丢了怎么办 惠阳居住证回执单丢了怎么办 南京居住证换地方了怎么办 买家退回的商品有问题怎么办 农转农户口手续怎么办 原房东不迁户口我怎么办