[置顶] linux下进程间通信的几种主要方式简介

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

  1. 管道(Pipe)及有名管道( mkpipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
  2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
  3. 消息队列(Message):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点
  4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。


一.管道

1无名管道:

    管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。

    管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,构成两进程间通信的一个媒介

    数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

   管道创建:

      #include <unistd.h>

        int pipe(int fd[2])

    管道两端可分别用描述字fd[0]以及fd[1]来描述,管道读端:fd[0],管道写端:fd[1];

2有名管道:

不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作

 

 

有名管道的创建:

    #include <sys/types.h>

    #include <sys/stat.h>

    int mkfifo(const char * pathname, mode_t mode)

    该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。

    第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以于FIFOclosereadwrite等等


二.信号

 

     信号是进程之间通信的另外一种方式。之前用过kill -l看了Linux系统支持的所有信号,这些信号在sys/signal.h中定义,系统支持64种信号。除了系统内核和root之外,只有具备相同uid、gid的进程才可以使用信号进行通信。
  1) SIGHUP  2)  SIGINT  3) SIGQUIT  4) SIGILL
  5) SIGTRAP  6) SIGABRT  7) SIGBUS  8) SIGFPE
  9)  SIGKILL  10) SIGUSR1  11) SIGSEGV  12) SIGUSR2
13) SIGPIPE  14) SIGALRM  15) SIGTERM  16) SIGSTKFLT    //本次程序提到信号
17) SIGCHLD  18) SIGCONT  19)  SIGSTOP  20) SIGTSTP
21) SIGTTIN  22) SIGTTOU  23) SIGURG  24) SIGXCPU
25) SIGXFSZ  26) SIGVTALRM  27) SIGPROF  28) SIGWINCH
29) SIGIO  30) SIGPWR  31) SIGSYS  34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12
47) SIGRTMIN+13  48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14
51) SIGRTMAX-13  52) SIGRTMAX-12  53) SIGRTMAX-11  54) SIGRTMAX-10
55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX 
gaolu@gaolu-desktop:~$
1、信号的产生
(1)kill函数
函数调用形式为int kill(pid_t pid,int sig),表示向进程pid发送信号sig。这个函数之前用过,不再介绍。
函数执行成功返回0,失败返回-1.
(2)raise函数
该函数用于给调用进程自身发送信号,调用形式为int raise(int sig);等同与调用kill(getpid(),sig).
函数执行成功返回0,失败返回非零数值。
(3)alarm函数
该函数用于给进程设置告警时钟,时钟(单位为秒)到达后,给进程发送SIGALARM信号。默认处理方式为进程直接终止运行,也可以修改捕捉信号后的默认处理函数。
三.消息队列

消息队列是一个消息链表,允许一个或多个进程向它写消息,另外的进程从中读取消息,具有FIFO的特性,但是可实现随即访问。在内核中,消息队列由"队列id"标识。

创建消息队列==>添加消息==>读取消息==>删除队列

<1>创建消息队列 msgget()

  int msgget(key_t key,int flag);

参数:key  为IPC_PRIVATE时,建立新的消息队列

           不为IPC_PRIVATE,根据flag是否有IPC_CREAT确定

      flag 标志位

返回:成功,返回消息队列识别代码msgid  失败 返回-1 错误信息存放在errno中

<2>向消息队列添加消息

  int msgsnd(int msgid,struct msgbuf *msgp,size_t msgsz,int flag);

参数:msgid  消息队列id 由msgget()得到

      msgp  存放消息类型和内容

 
  1. struct msgbuf{ 
  2.  long mtype; 
  3.   char mtext[1]; 
  4.  } 

msgbuf在头文件中未实际定义,需要自己定义。

       msgsz 消息大小

       flag 为IPC_NOWAIT时,未立即发送,调用进程立即返回

返回:成功,返回消息队列识别代码  失败 返回-1 错误信息存放在errno中

<3>从消息队列读取消息

  int msgrcv(int msgid,struct msgbuf *msgp,size_t msgsz,long msgty,int flag);

参数: msgid  消息队列id 由msgget()得到

      msgp  存放消息类型和内容

      msgsz 消息大小

      msgty 消息类型

      flag 为IPC_NOWAIT时,未立即发送,调用进程立即返回

返回:成功,返回消息队列识别代码  失败 返回-1 错误信息存放在errno中

 

<4>控制消息队列

  int msgctl(int msgid,int cmd,struct msgid_ds, *buf);

参数: msgid  消息队列id 由msgget()得到

      cmd  IPC_STAT   将消息队列信息写入buf

           IPC_SET    根据buf设置为消息队列

           IPC_RMID   删除消息队列

返回:成功,返回消息队列识别代码  失败 返回-1 错误信息存放在errno中

四.共享内存

      共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。共享内存往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。 
首先要用的函数是shmget,它获得一个共享存储标识符。

#i nclude <sys/types.h> 
        #i nclude <sys/ipc.h> 
        #i nclude <sys/shm.h> 
        int shmget(key_t key, int size, int flag);

 

该函数为获取一个共享内存的标识符,其中key变量可以通过ftok()来获得

Size为需要的共享内存大小,shmflg是共享内存标志:IPC_CREAT、IPC_EXCL、;其中IPC_CREAT用于生成一个新的共享内存段,当IPC_CREAT与IPC_EXCL一起使用时,当所要创建的共享内存段已经存在时,将会返回一个EEXIST错误

 

   void *shmat(int shmid, const void *shmaddr, int shmflg);

该函数用于将一个共享内存段连接到地址空间中,其中shmid为共享内存段的标识符;

shmaddr:

(1) 如果shmaddr为0,则此段连接到由内核选择的第一个可用地址上。

(2) 如果shmaddr非0,并且没有指定SHM_RND,则此段连接到shmaddr所指定的地址上。

(3) 如果shmaddr非0,并且指定了SHM_RMD,则此段连接到(shmaddr-(shmaddr mod SHMLBA))

所表示的地址上。SHM_RND命令的意思是:取整。SHMLBA的意思是:低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。

除非只计划在一种硬件上运行应用程序,否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址。


五.信号量

    信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:

(1) 测试控制该资源的信号量。

(2) 若此信号量的值为正,则允许进行使用该资源。进程将进号量减1。

(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。

(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

信号量函数定义如下:
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);

semget函数创建一个新的信号量或是获得一个已存在的信号量键值。 

  int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops)

key是一个建我们可以用ftok函数来获得。key是一个整数值,不相关的进程将通过这个值去访问同一信号量。

函数semop用来改变信号量的状态,第一个参数,sem_id,是由semget函数所返回的信号量标识符。第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

struct sembuf {
    short sem_num;
    short sem_op;
    short sem_flg;
}
      第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。
       最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量,如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。
semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。我们可以在手册页中了解关于semop处理更为详细的信息

 int semctl(int sem_id, int sem_num, int command, ...);

第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。

通常的command值为:

SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

你可能感兴趣的:([置顶] linux下进程间通信的几种主要方式简介)