Linux征途——进程间通信
进程之间是独立的,但是进程之间还是要相互协作的,这种协作叫通信。
本片博文非常的长~~~请按需求阅读
目录
1》》目的
2》》通信方式
3》》管道
4》》共享内存
5》》进程信号
1、Linux信号的种类:
2、信号的产生:
3、信号的注册:
4、信号的注销:
5、信号的处理:
6、信号的阻塞:
6》》可重入函数和不可重入函数
7》》volatile关键字
1》》目的
数据传输:一个进程的数据发送给另一个进程
数据共享:多个进程共享同样的进程
通知事件:一个进程需要通知另一个进程发生了某种事件
进程控制:一个进程完全控制 另一个进程的执行
2》》通信方式
由于进程的独立性,通信需要双方拥有公共的媒介才能通信,而媒介由操作系统提供;由于通信的场景不同或者目的不同,操作系统提供了不同的进程间通信方式。
a、管道
b、共享内存
c、消息队列
d、信号量
ipcs 查看进程间通信方式(-m共享内存 -s信号量 -q消息队列)
ipcrm 删除通信方式
3》》管道
概念:
一个进程连接到另一个进程的数据流,称之为管道。这便可以达成一个目的——数据传输- 原理:
操作系统在内核创建一块缓冲区,缓冲区便就是管道,并为用户提供管道的操作句柄----->传输的是字节流
一共有两个句柄:fd[2]—— ( fd[0] 和 fd[1] ) fd[0]用于读取,fd[1]用于写入
- 特性:
1>若管道中没有数据,read读取数据,会发生阻塞,直到读到数据返回。
2>若管道中数据满了,如果继续写入数据,则会发生阻塞,直到能够写入数据或者写入完成。
3>若所有读端关闭,继续写入数据会触发异常。SIGPIPE
4>若所有写端关闭,进程会将管道数据全部读取完后退出程序。
5>声明周期随进程,自带同步和互斥
(同步:对临界资源访问的时序可控 互斥:对资源的访问唯一)
- 命名管道/匿名管道 ——都是半双工通信
- 匿名管道:
没有名字的缓冲区,因此在内核中,如果是两个独立的进程,即使其中一个进程创建了管道,另一个进程是找不到它的句柄的,所以匿名管道只能是用于具有亲缘关系的进程进行通信(fork()创建子进程,父子进程同时指向匿名管道)
int pipe(int fd[2])————成功返回0,失败返回错误代码
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5
6 int main()
7 {
8 int pipefd[2];
9 int ret = pipe(pipefd);
10
11 if(ret < 0)
12 {
13 printf("PIPE ERROR\n");
14 return -1;
15 }
16
17 int pid = fork();
18 if(pid < 0)
19 {
20 printf("FORK ERROR\n");
21 exit(-1);
22 }
23 else if(pid == 0)//子进程
24 {
25 char buff[1024];
26 read(pipefd[0],buff,1023);
27 printf("child readed:%s\n",buff);
28 }
29 else
30 {
31 char buff[1024] = {0};
32 scanf("%s",buff);
33 printf("father write:%s\n",buff);
34 write(pipefd[1],buff,strlen(buff));
35 }
36 wait(NULL);
37 return 0;
38 }
- 命名管道:
文件系统的可见性,有文件可见于文件系统之中。
创建一个带名字的缓冲区,操作和匿名管道相同。
open打开管道文件的特性:
若管道文件以只读方式打开,若没有被其他进程以写的方式打开,则被阻塞,直到被其他进程以写的方式打开这个管道文件
若管道文件以只写方式打开,若没有被其他进程以读的方式打开,则被阻塞,直到被其他进程以读的方式打开这个管道文件
管道的实例:管道符的实现 |
主进程创建两个子进程,在两个中分别进行程序替换让两个子进程分别运行ls程序和grep程序
ls 将结果写入到标准输出
grep从标准输入读取数据
4》》共享内存
进程间通信最快的一种
- 概念:在物理内存中开辟一块空间,并将空间映射到各个进程的虚拟地址空间的共享区,其他进程可以通过虚拟地址直接对内存进行操作。
- 共享内存的操作步骤:(shared memory------------>shm)
a、创建共享内存——指定标识符,大小,权限,并返回一个句柄 ---->shmget
b、将共享内存映射到虚拟地址空间——对句柄操作-------------------->shmat
c、对共享内存进行任意操作
d、解除映射关系----------------------------------------------------------->shmdt
e、删除共享内存----------------------------------------------------------->shmctl
int shmget(size_t key, size_t size, int shmflg)
key:共享内存的标识符/名称
size:共享内存的大小
shmflg:与mode_t权限类似
成功返回shmid,失败返回-1
void* shmat(int shmid, const void* shmaddr, int shmflg)
shmid:共享内存标识符
shmaddr:指定连接的地址
shmflg:SHM_RND\SHM_RDONLY
成功返回共享内存首地址 失败返回 -1
int shmdt(const void *shmaddr);
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096
int main()
{ //创建共享内存
int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT|0664);
if(shmid < 0)
{
perror("CREATE ERROR\n");
return -1;
}
//映射首地址
char* address = (char*)shmat(shmid,NULL,0);
//映射首地址
char* address = (char*)shmat(shmid,NULL,0);
if(address ==(void*)-1)
{
perror("CONECT ERROR\n");
return -1;
}
//进行内存操作
int i = 0;
while(1)
{
sprintf(address,"------%d",++i);sleep(1);
}
//接触映射关系
shmdt(address);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/shm.h>
#define IPC_KEY 0x123456
#define SHM_SIZE 4096
int main()
{ //创建共享内存
int shmid = shmget(IPC_KEY,SHM_SIZE,IPC_CREAT|0664);
if(shmid < 0)
{
perror("CREATE ERROR\n");
return -1;
}
//映射首地址
char* address = (char*)shmat(shmid,NULL,0);
//映射首地址
char* address = (char*)shmat(shmid,NULL,0);
if(address ==(void*)-1)
{
perror("CONECT ERROR\n");
return -1;
}
//进行内存操作
int i = 0;
while(1)
{
printf("收到:%s\n",address);
sleep(1);
}
//接触映射关系
shmdt(address);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
第一个程序运行之后,运行第二个程序,便会得到下面的图1。如果第一个程序停止,第二个的数据不会变化,图2。第二个程序停止在运行,数据不会从停止的时候开始,因为共享区的数据一直在被改变图3。
ipcs -m
ipcrm:删除该id之后,我并没有结束我的进程,所以并不会真正的删除我们的共享内存,当我们后面的链接数nattch为0的时候,就真正的删除了
5》》进程信号
信号:就像我们听到下课铃声,铃声响起(信号产生)--->我们听到,并且识别(信号注册)--->知道下课了,忘记这个提示(信号的注销)---->开始玩耍(信号的处理)/拖堂(信号的阻塞)
作用:事件通知——实际是软中断(软件中断)
1、Linux信号的种类:
查看:kill -l (1~31 非可靠信号,继承Unix 34~64 可靠信号(实时信号))
2、信号的产生:
就像下课铃声打了~~~~。信号产生。
- 软件方式:
函数:
int kill(pid_t pid, int sig):指定进程,指定信号
int raise(int sig):给调用进程指定信号
int abort(void):给调用进程发送SIGBART信号
size_t alarm(size_t second):second秒后发送SIGALRM信号
我需要每次睡眠一秒,不然不知道打印会很多很多~
总的代码。
- 硬件方式:
ctrl + c ctrl + z(只是停止进程,并未退出) ctrl + l
(信号的到来会打断当前可中断睡眠状态)
core dumped———核心转储:异常退出时保存程序运行信息--->默认关闭
ulimit -a 查看转储文件大小
ulimit -c 设置转储文件大小-----》只有设置了大小,才会生成core文件
core文件命名方式:core.pid
core文件的使用:
gdb 可执行文件 ---> core-file core.pid
3、信号的注册:
下课铃声进入我们和老师的大脑。
pcb中有 struct sigpending,该结构体中有sigset_t(信号集合——位图0\1)
操作系统给一个进程发送信号,实际就是向进程的信号pending集合中添加信号(修改位图)
位图只能标记信号是否存在,不能标记到来的信号的个数,这就牵出可靠信号和不可靠信号。
pcb中有sigqueue链表,信号到来会组织一个结点添加到链表中
可靠信号到来:修改位图,每个信号都组织结点添加到链表中
非可靠信号到来:修改位图,若位图已经为一,修改位则什么也不做,否则添加一个结点。
4、信号的注销:
铃声响完之后,铃声不会一直在脑海里
可靠信号:可能结点有多个,所以删除结点后判断是否有多个相同结点,判断是否修改位图
非可靠信号:结点只有一个,删除结点位图修改为0
5、信号的处理:
准备下课~~~~
当我们收到接收到一个信号,就需要来处理这个信号,也就是处理每个信号对应的操作。每个信号都有其默认的操作,但是操作系统也提供了修改其默认操作的接口。
- 1) signal(int signum, sighander_t handler)
这是给信号修改一个处理函数,这个也可以当成是信号的注册,因为它就是给信号赋一个操作。当出现signum号信号,开始执行这个handler操作。
signum:信号编号
handler:操作,操作有一下三种:
默认:每个结点对应了一个事件,每个事件在操作系统都有定义完毕的处理方式 SIG_DFL
忽略:忽略信号 SIG_IGN
自定义:用户自定义函数(回调函数),替换内核中的处理方式
我定义一个处理的函数,每当我发起SIGINT信号,就会去调我们的fun函数。
- int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;//通常为 0,调用第一个函数sa_handler,如果是SA_SIGINFO就调用第二个函数
void (*sa_restorer)(void);
};
sigaction函数可以读取和修改与指定信号相关联的处理动作。signal(int signum, sighander_t handler)内部也是调用的这个函数。
参数:igno是指定信号的编号。
若act指针⾮空,则根据act修改该信号的处理动作。
若oact指针⾮空,则通 过oact传出该信号原来的处理动作。
void fun(int num)//自定义操作
{
printf("action !! %d\n",num);
}
int main()
{
struct sigaction act;
struct sigaction old;
sigemptyset(&act.sa_mask);
act.sa_handler = fun;//自定义操作
act.sa_flags = 0;
sigaction(2,&act,&old);//不可靠信号
sigaction(SIGRTMIN+5,&act,&old);//可靠信号
//下面是信号的阻塞,在下下一小节/
sigset_t newset;
sigset_t oldset;
sigemptyset(&newset);
sigfillset(&newset);
sigprocmask(SIG_BLOCK,&newset,&oldset);//阻塞所有信号
printf("bolck………………………………\n");
getchar();
sigprocmask(SIG_UNBLOCK,&newset,NULL);
}
2号信号触发了4次,39号信号也调用了4次,但是不可靠信号之处理了一次。说明改信号只注册了一次,这就验证了前面两种信号的注册
- 信号的捕捉
处理操作是我们用户自定义操作的时候,进程会从用户态和内核态相互切换。
如果信号的处理动作是⽤户⾃定义函数,在信号递达时就调用这个函数,这称为捕捉信号
进程从用户态到内核态三种形式:系统调用接口,程序异常,中断
6、信号的阻塞:
就是说信号来了,但是暂时不处理这个信号,阻止信号的递达。就像下课铃声响了,老师拖堂,阻塞下课。
(注意: 9信号和19信号不能被阻塞和自定义)
- int sigemptyset(sigset_t *set); 清空信号集合
- int sigfillset(sigset_t *set); 将所有信号添加到集合中
- int sigaddset (sigset_t *set, int signo); 将指定信号添加到集合中
- int sigdelset(sigset_t *set, int signo); 删除指定信号
- int sigismember(const sigset_t *set, in t signo);
- int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 返回值:若成功则为0,若出错则为-1
SIG_BLOCK : 阻塞set集合中的信号,将原有阻塞放入oset
SIG_UNBLOCK: 对set集合中的信号解除阻塞
SIG_SETMASK: 将set集合中的信号添加到阻塞集合中
如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是⾮空指针,则 更改进程的信号屏蔽字,参数how指⽰如何更改。
如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到oset⾥,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
//如果oset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到 oset⾥,然后根据set和how参数更改信号屏蔽字
/*实现的过程:
*1、定义一个集合
*2、向集合哄添加要阻塞的信号
*3、阻塞这个集合中的所有信号
*4、获取换行getchar()
*5、对集合中的信号进行解除阻塞
*/
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main()
{
sigset_t newset;
sigset_t oldset;
sigemptyset(&newset);
sigfillset(&newset);
sigprocmask(SIG_BLOCK,&newset,&oldset);
printf("bolck………………………………\n");
getchar();
sigprocmask(SIG_UNBLOCK,&newset,NULL);
return 0;
}
//如果oset是⾮空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮空指针,则 更改进程 的信号屏蔽字,参数how指⽰如何更改。。
void print(sigset_t* p)
{
int i = 1;//信号的集合是从1开始的
for(i; i < 32; ++i)
{
if(sigismember(p,i))//判断指定信号是否在目标信号集中
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
}
int main()
{
sigset_t newset,oldset,pending;
sigemptyset(&newset);
sigfillset(&newset);
sigprocmask(SIG_BLOCK,&newset,NULL);
raise(5);//给调用进程发送5号信号
raise(6);//给调用进程发送6号信号
raise(7);//给调用进程发送7号信号
raise(8);//给调用进程发送8号信号
getchar();
sigpending(&pending);
print(&pending);//打印我们的未决集合
sigprocmask(SIG_UNBLOCK,&newset,&oldset);//解除阻塞
return 0;
}
//由于阻塞了所有信号,所以2号和3号也阻塞着。未决集合,注册了,但是没有处理的信号。
所以23 5678号信号都打印了
6》》可重入函数和不可重入函数
:重入,我们可以理解为多个执行流可以进入。函数被不同的控制流程调⽤,有可能在第⼀次调⽤还没返回时就再次进⼊该函数, 这称为重⼊。那么这样我们得考虑数据安全的问题。所以能否在多个时序操作中对全局数据能时序性的访问,也就是全局数据是正常的改变就是重入,反之就是不可重入。看下面的例子
//C语言
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int a = 1;
int b = 1;
int test()
{++a;sleep(4);++b;return a+b;}void sigcb(int num){printf("signal-----> %d\n",test());}int main(){signal(SIGINT,sigcb);//CTRL + C 触发printf("main-----> %d\n",test());return 0;}
//如果我们的主控流程先执行,即信号后执行,我们预期结果是不是应该是
signal----->6
main----->4
但是………………4秒类按CTRL + C,不然触发不了信号。
为什么和预期结果不一样呢?
这就是因为这个test是不可重入的函数,也就是说如果有其他执行流进入,会造成数据错误,造成数据不安全问题。在4秒类,a先++、a==2,等四秒,这时信号进来,a++、a==3、,b++,b==2,然后主控流程的printf最后执行的时候,b++、b==3,得到上面的结果。
7》》volatile关键字
保持内存可见性,防止编译器对代码过度优化。看下面的例子
//C语言#include<stdio.h>#include<signal.h> int a = 1;void sigcb(int num){printf("signal-----》%d\n",num);//CTRL+C,触发这个操作,改变a的值a = 0;}int main(){signal(SIGINT,sigcb);while(a)//值改变之后,while退出循环{ }return 0;}
//真的是这样的吗??
有没有发现我编译的时候加了-O2,这就是让编译器对代码进行优化。对于上面的代码,如果a这个数据用的频率偏高,而且值一直没有变化,所以它就将a的值放入寄存器中,那么CPU在寄存器中拿数据就不去内存中了,但是我们改变的是内存中的a的值,所以a的值并没有改变。这时我们对 a 加个关键字volatile。
//C语言
//C语言#include<stdio.h>#include<signal.h> volatile int a = 1;void sigcb(int num){printf("signal-----》%d\n",num);//CTRL+C,触发这个操作,改变a的值a = 0;}int main(){signal(SIGINT,sigcb);while(a)//值改变之后,while退出循环{ }return 0;}
//真的是这样的吗??
这时一下就退出了,加这个关键字的时候,编译器就不会把a加入到寄存器中。
Linux征途——进程间通信相关推荐
- linux c 进程间通信
进程间通信概述 进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息. Linux的进程间通信方法有管道(Pipe)和有名管道(FIFO).信号( ...
- Linux的进程间通信-消息队列
Linux的进程间通信-消息队列 微博ID:orroz 微信公众号:Linux系统技术 前言 Linux系统给我们提供了一种可以发送格式化数据流的通信手段,这就是消息队列.使用消息队列无疑在某些场景的 ...
- Linux环境进程间通信(二): 信号--转载
http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html http://www.ibm.com/developerworks ...
- Linux下进程间通信的六种机制详解
linux下进程间通信的几种主要手段: 1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...
- Linux下进程间通信概述
1. Linux下进程间通信概述 P83-P84 将第一页和第二页合并起来讲了 引言:前面我们学习了一下进程,我们知道多,进程间的地址空间相对独立.进程与进程间不能像线程间通过全局变量通信. 如果想进 ...
- Linux环境进程间通信 信号量
信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制.相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志.除了用于访问控制外,还可用于进程 ...
- Linux进程+进程间通信IPC
一 Linux进程 1) 进程的内存映像 2)解释 BSS段:在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域.BSS是英文Bloc ...
- 通信开源linux,Linux环境进程间通信
Linux环境进程间通信(一) http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/index.html Linux环境进程间通信(二): 信 ...
- Linux环境进程间通信(五): 共享内存(上)
Linux环境进程间通信(五): 共享内存(上) 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间. ...
最新文章
- [概统]本科二年级 概率论与数理统计 第七讲 期望、方差与相关性
- PyCairo 中的文本
- 14行代码AC——习题5-4 交换学生(Foreign Exchange, UVa 10763)——解题报告
- SAP CRM产品主数据无法根据产品描述字段进行搜索的原因
- 群签名和环签名的区别_环签名方案的研究
- mysql 同一天多条记录只取第一条_MySQL面试高频100问(二)
- java中操作时间的常用工具类
- OSI七层、TCP/IP五层、UDP、TCP的socket编程(服务端及客户端)、字节序转换、多进程以及多线程服务端的实现
- 【ElasticSearch】深入理解 ElasticSearch Doc Values
- 输出大写字母矩阵c语言,寻找C语言大神!!从键盘输入任意一个字母,如果其为大写字母,则输出“capital letter”;如果其为小写...
- SWPU ROUND #6(DIV.3)
- 学术族谱典型用户及典型场景模拟
- 泰勒公式的计算机应用,泰勒公式应用
- 谷歌学术打不开的解决办法
- 自我鉴定计算机专业大学,大学生计算机专业的自我鉴定书
- 微信小程序后台销毁时间 演变和总结(热启动时间限制)
- PCM1863应用笔记
- HTML实现A4模板
- Kali渗透测试(四)——无线网络WPA攻击(PSK破解、AIROLIB、JTR、cowpatty、pyrit)
- 创建一个docker容器