Linux进程间的通信

Linux进程间的通信

常见的进程间通信方式:

管道(有名/匿名管道)、消息队列(systemv、posix两个版本)、内存共享映射(MMAP)、网络套接字

信号量、条件变量、互斥锁、文件锁、进程锁、信号等

 

管道

绝大多数进程间的通信都是基于内核区域

在内核中建立一个缓冲区, 两个进程向缓冲区读/写数据(一般只能取一次)

匿名管道、有名管道

管道大小:4096Byte(根据版本有不同:Ubuntu16.04)

结构:环形队列

匿名管道

函数

int pipe(int fds[2]);

参数:

传出管道的两个访问方式:读:fd[0], 写:fd[1]。

返回值:

成功返回0, 失败返回-1

 

使用方式:进程A和进程B通信

1. 进程A调用pipe()创建管道,获取管道的访问方式

2. 进程B要访问这个管道,需要获取管道的访问方式:进程B如果是进程A的子进程, 则进程B会继承进程A的访问方式

3. 确定通信方向:让一个进程只保留读访问方式, 一个进程只保留写访问方式

close(fd[0]);
close(fd[1]);

4. 通过wirte写, read读

 

https://www.cnblogs.com/xkDiogt/p/13551918.html

 

特点

匿名管道只能完成亲缘之间的通信。

读写端没有规定每次发送/读取数据的大小, 读写时可能产生异常。

单工通信:只能读或写

如果管道写端关闭, 则读端会读到0(文件末尾)

如果管道读端关闭, 则写端会直接被杀死(SIGPIPE信号)

读写端都存在, 管道被写满后, 写端将会阻塞

读写端都存在, 管道中没数据, 读端将会阻塞

 

有名管道

1. 创建有名管道:

匿名管道创建出一个内核缓冲区, 有名管道创建内核缓冲区和一个管道文件

创建管道文件(不需要手动创建内核缓冲区)

(1)命令

mkfifo filename # 通过命令创建管道文件

(2)函数

mkfifo(char *names, int mod);

有名管道对管道文件文件描述符的读写, 实际上是对内核缓冲区的读写。

管道文件只是提供一个操作内核缓冲区的方式, 不允许编辑。

 

https://www.cnblogs.com/xkDiogt/p/13552017.html

 

可以进行非亲缘进程间的通信

半双工

使用有名管道时, 进程需要足够的权限(RDWR), 否则会阻塞

 

内存共享映射(MMAP)

1. 创建映射文件(也可以是一个内存块):size != 0(touch mapfiles即可)

2. 在进程中创建一块映射内存

3. 映射方式:私有映射、共享映射

  私有映射:将映射文件中的内容拷贝到进程的映射内存, 两块内存互不影响

  共享映射:使用同步机制, 修改一方, 另一方也会改变。

 

函数

mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // 创建进程中的映射内存

参数:

1. NULL:系统自定义映射空间, 也可以自定义

2. 映射文件大小, 不可以为0

3. 映射内存访问权限

4. 映射方式

5. 映射文件文件描述符

6. 映射偏移量, 只能是4K整数倍

 

返回值:

成功返回映射空间地址, 失败返回NULL

munmap(void *p, size); // 释放映射内存

 

https://www.cnblogs.com/xkDiogt/p/13553031.html

 

 

信号

将信号发给不同的进程可以终止、挂起进程等。

查看系统的信号命令:

kill -l

1~31:Unix经典信号

SIG开头:

% kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

32~33:线程库使用(隐藏)

34~64:自定义信号/实时信号(硬件)

 

发送信号的方式

需要有足够的权限

1. 终端组合按键:

ctrl+c # 2号信号:SIGINT
ctrl+\ # 3号信号:SIGQUIT
ctrl+z # 20号信号:挂起进程

2. 通过kill命令发送信号:

kill -信号编号 进程pid # 发送指定信号到指定进程

3. 通过函数产生信号:

kill(pid_t pid, int signo); // 进程pid, 信号编号
raise(int signo); // 向自己发送信号
abort(); // 向自己发送SIGABRT信号
sigqueue(pid_t pid, int signo, union sigval); // 可以发送信号, 也可以携带信息(进程通信)
pthread_kill(pthread_t tid, int signo); // 向线程发送信号

4. 硬件异常产生信号:

例:非法操作内存内核发送信号(段错误)

5. 浮点数例外导致异常终止(浮点数例外)

6. 调度异常导致异常终止

7. 软条件产生信号

例:定时器(SIGARRM)

例:管道读端关闭发送信号杀死写端(SIGPIPE)

 

信号的三种行为和处理动作

1. 默认行为:SIG_DFL

五种默认动作:

  TREM 杀死进程

  CORE 杀死进程同时转储核心, 生成core文件

  IGN 忽略动作, 进程不会受到任何影响

  STOP 挂起进程

  COUNT  唤醒进程

2. 忽略行为:SIG_IGN

没有任何动作

3. 捕捉行为:SIG_ACTION

自定义动作

 

信号失效方案:信号屏蔽(信号阻塞)、信号忽略、信号捕捉

 

信号发送的流程

https://blog.csdn.net/flowing_wind/article/details/79967588

当我们在shell上写出一个死循环退不出来的时候,只需要一个组合键,ctrl+c,就可以解决了,这就是一个信号,但是真正的过程并不是那么简单的。

1、当用户按下这一对组合键时,这个键盘输入会产生一个硬件中断,如果CPU正在执行这个进程的代码时,则该进程的用户代码先暂停执行,用户从用户态切换到内核态处理硬件中断

2、终端驱动程序将这一对组合键翻译成一个SIGINT信号记在该进程的PCB中(也就是发送了一个SIGINT信号给该进程)

3、当某个时刻要从内核态回到该进程的用户·空间代码继续执行之前,首先处理PCB中的信号,发现有一个SIGINT信号需要处理,而这个信号的默认处理方式是终止进程,所以直接终止进程,不再返回用户空间执行代码。

注意:ctrl+c只能终止前台进程。一个命令可以加&可以将进程放在后台执行,这样shell就不必等待进程结束就可以接收新的命令,启动新的进程

2、shell可以同时运行一个前台进程和多个后台进程,只有前台进程才能收到ctrl+c这种组合键产生的信号

3、前台进程在 运行过程中用户可以随时按下ctrl+c产生一个信号也就是说前台进程的用户空间代码执行到任意一个时刻都可能接收到SIGINT信号而终止,所以信号对于进程的控制流来说是异步的

 

如果未决信号集对应位为1时, 信号过来, 不支持排队, 直接丢弃。

未决信号集:只有内核可以设置。

阻塞信号集:用户可以设置, 用于阻塞/屏蔽某个信号, 无法抵达, 使其暂时失效。

 

信号屏蔽

sigset_t 信号集类型
int sigemptyset(sigset_t *set); // 初始化信号集为0
int sigaddset(sigset_t *set, int signum); // 设置指定位
int sigdelset(sigset_t *set, int signum); // 清除指定位
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 设置新的信号集

 https://www.cnblogs.com/xkDiogt/p/13556256.html

int sigpending(sigset_t *set); // 获取未决信号集
int sigismember(const sigset_t *set, int signum); // 检查信号集中指定位是否为1

 

忽略信号

通过修改信号行为, 而不是设置屏蔽字

sturct sigaction // 信号行为类型
{
    sa_handler; // 信号行为
    sa_flags; // 选项, 0:旧接口
    sa_mask; // 临时屏蔽字
};
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); // 替换信号行为

 

 https://www.cnblogs.com/xkDiogt/p/13556338.html

 

捕捉信号

通过修改信号行为, 自定义动作

 https://www.cnblogs.com/xkDiogt/p/13556367.html

 

信号在内核空间中, 自定义动作在用户空间, 完成自定义动作后, 回到内核

 

可能引发的问题

1. 进程阻塞和挂起时, 收到信号会中断, 进程会被意外唤醒

2. 自定义信号动作, 如果修改全局变量, 可能会造成异常

可重入和不可重入函数:该函数是否使用了静态或全局变量, 自定义行为中是否可以调用

 

SIGKILL 和 SIGSTP 信号, 无法被屏蔽、忽略、捕捉

 

使用信号进程进程间通信

1. 进程A和进程B通信, 发送指定信号并携带数据, A和B对信号进行捕捉, 捕捉函数的参数就是数据

sa_sigaction; // 替换sa_handler新的接口函数:void sig_job(int code, siginfo_t *info, void *);
sa_flags = 1; // 代表新接口
siginfo_t:// si_int, si_ptr 数据

 

2. 函数:

sigqueue(pid_t pid, int signo, union sigval); // 可以发送信号, 也可以携带信息(进程通信)
union sigval // 要么发送整型, 要么发送指针
{
    int sival_int;
    void *sival_ptr;
};

 

3. 信号:

SIGUSR1, SIGUSR2信号可以用来完成进程间通信

 

 https://www.cnblogs.com/xkDiogt/p/13556708.html

子进程会继承父进程的屏蔽字

posted @ 2020-08-24 20:40  x_Aaron  阅读(358)  评论(0)    收藏  举报