进程间通信

信号

信号诞生

    信号的生成来自内核,让内核生成信号的请求来自3个地方:

1.用户:用户能够通过输入CTRL+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号;

2.内核:当进程执行出错时,内核会给进程发送一个信号,即向进程信号队列上添加一项。例如非法段存取(内存访问违规)、浮点数溢出等;

3.进程:一个进程可以通过系统调用kill给另一个进程发送信号,一个进程可以通过信号和另外一个进程进行通信。发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort(),以kill函数为例:

int kill(pid_t pid, int sig);

sig是要发的信号,如SIGINT等。pid是对方进程的pid,有以下几种情况:

pid > 0        //将信号发给ID为pid的进程

pid = = 0      //将信号发送给与发送进程属于同一个进程组的所有进程

pid < 0        //将信号发送给进程组ID等于pid绝对值的所有进程

pid = = -1    //将信号发送给该进程有权限发送的系统里的所有进程

信号处理函数的注册

    即向私有sigpending中添加sigpending结构体,向sigaction中添加信号操作函数;

那公有sigpending有什么用呢?

    电源出问题,则会向公有sigpending中添加信号量,向sigaction中添加信号操作函数,然后所有进程都会执行该信号对应的操作。


 signal永久注册,signal (int signum, handler)

 sigaction临时注册只执行一次,下次重新注册。int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  //signum是注册信号。最后一个参数是保存旧的行为,往往不保存旧的行为,所以写NULL。第二个参数act是 注册的信号处理函数。

信号的处理

    中断或者系统调用返回到用户态前检查信号量,执行完后,再执行return_from_syscall回到用户态。


进程接收到信号以后,可以有如下3种选择进行处理:

1. 默认处理(SIG_DFL):信号由该特定信号的默认动作处理。

2. 忽略信号(SIG_IGN):忽略信号,即使没有意义,代码执行仍将继续。

3. 函数处理程序:定义一个特定的函数来处理信号。

signal (int signum, handler)

handler可以为SIG_DFL、SIG_IGN、自定义handler。其中SIG_DFL、SIG_IGN同handler一样都是函数指针。

信号阻塞

int sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));

int sigpending(sigset_t *set));

int sigsuspend(const sigset_t *mask));

无名管道

    主要用于父进程与子进程之间,或者两个兄弟进程之间。在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立。因此,每个管道都是单向的,当需要双向通信时就需要建立起两个管道。管道两端的进程均将该管道看做一个文件,一个进程负责往管道中写内容,而另一个从管道中读取。



具体细节:fd指向file_struct->inode->pipe内存区

#include

intpipe(int fd[2])

有名管道

命名管道是为了解决无名管道只能用于近亲进程之间通信的缺陷而设计的。命名管道是建立在实际的磁盘介质或文件系统(而不是只存在于内存中)上有自己名字的文件,任何进程可以在任何时间通过文件名或路径名与该文件建立联系。

即相当于在磁盘上创建一个文件,别的进程想与该进程通信,往文件中写数据即可

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char * pathname, mode_t mode)

共享内存

1、mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存)(这里一个问题,需要高手解答,会不会太多拷贝到主存里面???);缺点:进程间读取和写入速度要比主存的要慢。

2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)

使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用shm。

物理内存空间共享


    两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信,而且共享内存是IPC通信当中传输速度最快的通信方式没有之一

#include <sys/ipc.h>

#include <sys/shm.h>

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

磁盘内存空间共享

    mmap优点共有一下几点: 1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。 2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。 3、提供进程间共享内存及相互通信的方式。


#include <sys/mman.h>

void *mmap (void *addr, size_t length, int prot, int flags, int fd, off_t offset);

消息队列

    消息队列是Linux内核地址空间中的内部链表,各个进程可以通过它来进行消息传递。

    进程发送的消息会顺序写入消息队列之中,且每个消息队列都有IPC标识符唯一地进行标识。简单理解就是,每个消息队列都有一个ID号,而这个号用来区分不同的消息队列,从而保证不同消息队列之间不冲突。而每个消息队列内部也维护了一个独立的链表。


    消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。

    与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

# include <sys/types.h>

# include <sys/ipc.h>

# include <sys/msg,h>

int msgget(key_t key, int msgflg);

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

int msint msgrcv(int msqid, void *msgp, size_t msgsz,  long msgtyp, int msgflg);gctl(int msgid, int command, struct msgid_ds *buf);

常用消息队列(中间件broker)

企业型:ActiveMQ和HornetQ,这两个MQ从诞生起就是为了企业应用而设计的,JMS规范本身也是企业应用系统的规范。

互联网型:LinkedIn出品的Kafka,阿里出品的MetaQ

轻便型:memcacheq,Redis、zeroMQ

socket

进程与端口号、ip地址绑定,数据报头部放上端口号和ip地址,比较进程ip:port与数据报头部,相同则传送给该进程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容