linux:进程之间的通信

ipc :进程间通信(InterProcess Communication)

1、管道 

同一时间是单向的:父读子写,或父写子读 

管道中的数据 ,读走就没了

 参数是一个整型数的数组,数组的大小是两个,返回值是一个整型数

pipe函数可以建立一个管道

我们不确定到底是父进程先运行还是子进程先运行

如果是子进程先运行的话,管道里面没有东西,孩子读取read 会堵塞,cpu就会被父亲争夺到,父亲就会关闭读端,往管道里面写

关闭读端并不是把整个管道关闭了

1-a(无名管道)

看一下man 2 pipe的用法

包含一个头文件

创建管道的原型

因为管道是基于父子进程之间的通信,所以用到fork

他的头文件,它不需要传参的

fork 有个返回值  int pid;

fork 以后 父进程会拷贝一个空间给子进程,包括pipe 出来的fd,所以他们的管道是同一个管道,

因为这个fd 指向内核中的管道位置都是一样的

系统调用在man 手册的第二章节

代码如下:

分析一下代码 ,main函数进来,调用pipe创建管道,pipe 需要一个变量fd,fd是个数组

创建成功后调用子进程

pid <0代表子进程创建失败

pid>0代表进入父进程的空间,父亲负责写,fd[0]是读端,fd[1]是写入端

(父进程提前退出了),父进程可以等待子进程退出,子进程也可以意思一下

我们不知道fork 以后父子进程谁先运行,假设我们想让子进程先运行,那就让父亲上来先睡个三秒

因为管道里面没有数据,所以会先阻塞

无名管道的意思:pipe出来      我们只能拿到读端和写端,我们并不知道管道的名字是什么

并且他只是存活在内存当中,在磁盘上面除了我们编写的代码和程序以外就没有其他的文件啦,

无名管道不以文件的形式存在磁盘当中的

1-b命名管道

 

 第一个参数是管道名

第二个参数是mode_t

mkfifo 在main手册的第三页

如何创建管道呢?  ./加上文件名

./加上文件名

./加上文件名

0600可读可写的方式

                                                   

 代码如下

运行的结果:生成了一个file

ls -l 列出文件的详细信息,如创建者,创建时间,文件的读写权限列表等等

file是一个p类型的,可读可写

然后我们可以打印一些调试信息,根据返回值

         

        

失败的原因:文件已经存在

路径文件已经存在,返回这个错误

看看perror 能不能找出来错误

文件存在

 

我们不希望文件存在的时候会出错,我们可以这样做

不会随意提示我们失败了还是其他的

我们先创建这个文件 ,如果mkfifo("./file",0600) =0  代表创建成功,条件不满足,执行下一条

如果文件存在mkfifo("./file",0600)就会变成 -1

还能在加点判断

如果file 不存在的话,运行一下

如果再运行呢?

 

为了避免打印一些错误的乱七八糟的错误消息

 

运行结果

    1-c命名管道的数据通信编程实现

 如果file 有的话不做任何事,file 没有的话创建file

去打开这个管道

 

包含头文件

 

我成功运行了 ,但是程序结束不了,没有任何的输出,也就是说 我打开管道,下面的那一句没办法执行

 

写的话就不创建fifo啦

先运行read 会阻塞

然后再调用write

 

这个时候其他进程打开,他才会往下走

 

那么如何使用FIFO呢?

 先修改一下read.c,从fd 里面读,读到buf,一次读30个字节

再修改write.c,往fd里面写

卡在这啦

 然后运行write

读了17个,内容是:message from fifo

 

 

这就实现了两个进程之间的通信

那write可不可以一直写呢?

防止速度过快,让他每秒写一下

 

然后就可以实现一直发,一直收

 

一般呢,都是让他阻塞,不阻塞就失去意义啦

 我们试着用非阻塞的方式打开

 

有点怪怪的感觉

 

 

管道的目的就是做数据交互

2,消息队列

因为它是一个链表,链表中每一个消息应该是一个结构体,因为链表的每一项节点,就是一个结构体

每一个队列都有消息,消息有类型和内容

A能获取B的数据,或者B能获取A的数据

步骤:

获取一个队列

读队列

另一个:

获取一个队列

写数据到队列

创建队列:msg队列的意思,get创建或者打开的意思

key是一个索引值,我们通过这个索引值,在内核中去找到某个队列

flag是打开队列的方式

最终返回一个int型的类型,int型是队列的id

A跟B都要通过一个key在内核当中找到或者创建一个队列

所以这个key,A应该知道,B也应该知道,A,B用的key应该是一样的

第二种情况是私有的

如果

key不一样,在队列里面找不到同一个队列

发送消息:

第一个是对列的ID啦,第二个是指针,第三个是消息的大小,第四个flag是打开队列的方式

 

 

读取消息:

第一个是对列的ID啦,第二个是消息的放的地方,第三个是放多大,第四个是队列的类型,,第四个flag是打开队列的方式

(你要读取哪个节点可以根据类型来找

关闭队列(或者说对列的其他操作):

2-b用消息队列API,实现消息队列的消息共享的编程

第一步肯定是获取

 

 再根据 

有的话直接获取。没有的话直接创建,再加上队列的权限,0777(可读,可写,可执行)

msgget的返回值是一个整型数

读队列如何读呢?

 

 我们看一下接收

第一个参数是队列的id

第二个参数是我们的消息

第三个参数是消息的长度

第四个参数是消息的类型

第五个参数是默认的值

一般我们推荐msgbuf 的结构体这样写,里面有两项内容,一项是我们想要的mtype,另一项是我们真正的内容,内容一个不够,一般可以128个字节

 

read怎么读呢?read肯定要读到这个buf 里面来

所以要定义一个Buf

 要把readBuf 的值,放进来

 读多大呢?一个结构体的大小(mtest)

                        

 消息的类型呢?(mtest)

                

再来默认的值

以默认的方式,接受消息,读不到888类型的话,会一直阻塞,知道读到为止(mtest)

把读到的消息队列打出来

完整读代码 

给他换个名字

编辑完读的,我们再来编辑写的文件

发送我们要构造一个buf, sendbuf是一个结构体

然后通过同样的索引值找到消息队列 

 

找到以后调用msgsnd

第一个参数,是消息队列的id

第二个参数是你要发送的消息队列,是一个地址

第三个参数,是你要发送内容的大小

        

 代码实现

0是非阻塞的方式

 完整写的代码

        

 

运行一下get ,get 阻塞在这边

这句话没打印,说明消息队列已经有了

 

发送一下数据

 

对比一下

 

(qall 退出)

如果说这让两个队列互相通信呢?

我发完以后再读咋办?

       (从msgId里面读,读到readBuf里面,读的长度sizeof(readBuf.mtext),读的消息类型988,

读完以后再把数据发出来。)

你收到别人的数据,是不是还要给别人发回去?

 

 具体操作如下

先是堵塞

 

 

 

(补充:可以查看历史指令)

 

2-c 键值生成以及消息队列移除

 函数原型:第一个是路径名,第二个是一个整数,返回值是key_t

.代表当前路径,1代表随便小的一个数

一般来说,通过路径名以及这个1配合会生成key值

 那路径名放在这边,用的是路径的什么?

用的是路径的索引节点

 当前路径的索引节点是多少?

查询文件索引节点号的方法是: ls -i

.代表当前文件

两者配合使用

 

修改一下之前的代码

你的id值随便,写个 23 可以,写个字符‘z’ 也可以

key以16进制打出来

另一个也改一下,都是用的 . 加上'z'

 

key的值一样,代表在内核里面找到的消息队列是同一个队列,就可以用这个队列进行通讯

 

 

每两个进程,通过一个消息队列,去做通讯,我们通过key来找

当我们找的key在内存里面没有这个队列,我们会去创建一个新队列,这样的事做多了,导致内核当中这个队列很多,有没有办法,用完队列就给他干掉?

第一个是队列的id

第二个是指令

第三个是一般写个NULL;

指令有哪些指令呢?

有下面四个

一般用的最多的IPC_RMID,它的意思是把消息队列的链表,从内核中移除

修改代码

 (补充:有时候打完代码,异常退出导致生成了swp文件,如何办?把swp文件删掉就好啦)

一打开文件就这样了

看到那个q!没有

rm   .文件名.c.swp  

 

 然后就可以正常使用了



3、共享内存

1.创建共享内存/打开      shmget

2.映射           shmat

3.数据的交换        

4.释放共享内存     shmdt

5.干掉      shmctl

如果A里面有个指针,刚好指向共享内存(int *p = 共享内存)

这个共享内存虽然不在A的存储空间,但是A可以挂载上来,把里面的内容直接打出来(printf("%s",p) )

B也是一样的

如何写入呢?

就和操作普通字符串一样,用strcpy把想写的话,写入共享内存

3-b共享内存编程实现

1.创建共享内存/打开      shmget

2.映射           shmat

3.数据的交换        

4.释放共享内存     shmdt

5.干掉      shmctl

共享内存的API

创建共享内存/打开

第一个参数是一个key,跟消息队列的key一样

注意第二个:共享内存的大小必须以兆对其的

第三个:假如我们写的时候,创建一个共享内存,shmflg可以配合IPC_CREAT,代表创建内存,共享内存必须得有权限

 成功返回共享内存的id,失败返回-1

 

exit正常退出返回0 ,异常返回-1

映射   

第一个获取共享内存的id

第二个一般写0,让linux 内核自动为我们安排共享内存

第三个一般也写0,0代表映射进来的内存,可读可写,除非你不想让你的内存具备可读可写的属性,可以加一些东西

 

at会把共享内存挂载到进程的存储空间,那么进程的存储空间里面,我们定义一个变量,指向共享内存,一般定义一个指针

共享内存映射完,就可以操作了,用strcpy给内存地址赋值

然后去卸载共享内存

上面的at 那个第一个是id

第二个是0

第三个也是0

直接把映射的地址放进来

挂掉共享内存,把整个共享内存都去掉,防止一直占用内存

第一个是id

第二个是指令

第三个是一般写0,这个buf用来存放卸载内存的时候,产生的一些信息,我们不关心这些信息

这些信息有什么呢?(时间呀,大小呀)

 

那cmd 有哪些指令呢?(消息队列删除的时候,用的也是IPC_RMID)

关于写的完整代码

写的写完了,然后我们来读

读的时候我们只要获取,不创建

那面删了,这面就不删了(A干掉共享内存,希望B程序退出以后再干掉,所以A多了5秒sleep)

关于读的完整代码

如何查看系统当中有哪些共享内存?

ipcs -m

我们去修改一下代码,变成创建共享内存

 加了第20 行

创建的共享内存大小是4兆,并没有设备去连接它(nattch)

 

那么如何把共享内存删掉呢?

ipcrm -m 加上shmid

(拓展:如果A ,B都往共享内存里面写,数据会出现一些问题,可不可以A写的时候B不可以写,解决的办法就是信号量来控制)

4、信号

 在系统级的应用当中,信号0被占用

对我们信号层来说,信号是从1到64的

 查看系统中的所有信号

kill -l

 (1)SIGHUP    挂起的意思

(2)SIGINT     中断的意思     就是ctrl +c 的信号

(3)SIGQUIT   退出

(4)SIGILL      出现的问题

(6)SIGABRT    丢弃

(7)SIGBUS   总线信号

(9)SIGKILL  杀死进程

(14)SIGALRM  闹钟信号

(19)SIGSTOP   停止信号

(29)SIGIO     IO口的访问

 

查找 

ps -aux|grep a.out

 

可以 kill -9 加上id 号

或者kill -SIGKILL 加上id号

所谓的实现异步通讯的手段 就是 捕获信号

4-b信号编程

 

这做的是信号的绑定

我们先来用信号的捕捉:

现在想实现一种效果,键盘按下ctrl +c 没办法停止我

这是一个函数指针,返回值是void 型,它的参数是一个整型数,中间括号是函数名

它的返回值是 signal_t

它的第一个参数就是我们说的信号(那64个),要捕捉的信号

第二个参数就是上面的结构体指针了,sighandler_t,_t就是结构体的意思,hanlder就是一个函数指针,指向这种形式的void (*sighandler_t)(int)函数

 

 

具体的代码

 按一下ctrl +c,获取到的signum是2,2不就是SIGINT,

因为系统收到的默认动作应该是终止我的进程,那我现在捕捉信号,修改默认动作,让他执行,我希望它执行的函数

杀死它

 

我们还可以注册其他的信号

这边的kill不是杀死,是发指令的意思

 

 

虽然对他修改可还是改不动

 

我们现在都是通过键盘,通过这个指令给它发信号,我能不能写个程序对他完成信号的发送?

用到的函数是kill函数,

main函数启动以后去杀进程,怎么杀呢?我希望传递一个信号值,以及pid 号,所以我要传参

 我们char 进来的是字符串,signum 和pid 是整型数,所以对他做一下转化

 我们做一下测试,信号值是9,pid 的值是2345

如果你没有转化的话,9和2345是字符串呀

我们看一下如何发送信号

一个是进程号,一个是signum

先运行之前那个程序

找到它对应的pid 号

 

 我们想要发送的是9

 

 这就是通过软件编程实现,捕捉信号以后对他的信号值装配

第一个函数是信号的编号,第二个是函数处理的函数指针

 

那我们如何发信号呢?要注意参数传进来,要进行转化,然后调用kill指令,发送指令

 

 还有一种写法,调用system

要构造这么一串字符串

 有个函数叫sprintf

第一个参数,是我们需要构造的目标字符串

第二个参数,是我们要做出来的字符串的长相

用system调用脚本来杀

 

 

那么如何忽略信号?用这个宏就可以

 找到那个文件

 

 

ctrl+c 被忽略了

 

那么我们试着杀它,能不能忽略


杀死它不能被忽略

 

 

 sigaction 的函数原型

第二个参数是一个结构体

第三个参数用来做备份的

sigcation里面的

第一个函数指针1

第二个函数指针2

第三个 mask结构体,起到阻塞的作用

第四个参数是一个整型数 ,有一个标记

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

 

 

 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

 

第一个参数发给谁

第二个发的是什么信号

第三个就是我们要的消息

 

精彩博客:

Linux 信号(signal) - 简书 (jianshu.com)

4-c信号携带消息,编程实战

等会需要调用这个函数

 

 

 

 假设我想捕获SIGUSR1,

第二个参数是我想干嘛

第三个参数作为备份

这就完成了sigaction的调用

 现在要做的就是把第二个参数做出来,

要配置sigaction是谁

要接收数据flags是谁

 作用:获得消息

 

 把地址传进来

 函数如何处理呢?

 我们给他补充完整

 siginfo_t 有多少内容呢?

 

这也是以一个结构体

 si_value也是一个结构体,也可以把pid 号拿出来

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

思路;

首先注册信号

第一个是收哪个信号

第二个参数是我想干嘛

第三个参数作为备份

 第二个参数要受到信号就要指定里面的flag

 收到信号调用handler处理信号

 

hanlder 收到信号把信号的值打出来,接着把内容打出来,内容是否有取决于context,context非空的话,我内容就有,我们把info里面si_int的数据打出来

完整的代码

 

 接着我们发信号

用到的函数

第一个是信号的pid 号 

       第三个是一个联合体

 

完整的代码

 

运行结果

 

 

 那我们是不是可以把pid 号打出来呀?是不是也可以把发送者的pid 也打出来

 

 

那我们再来修改一下send.c那个程序

send把我自己的pid 也打出来对比一下

运行结果

 

 

谁发的,发的内容是什么,都知道(可以发送整型数,也可以发送字符串)

 

 5、信号量

信号量用来管控临界资源

 信号量集:

p 操作,拿锁

v操作,放回锁

 

 5-b信号量编程实现

第一个key 按消息队列和共享内存的经验

第二个信号量集中的信号量的个数

第三个IPC_CREAT(熟悉不?不熟悉看之前的)

 

 

初始化

 

第一个参数是我们操作的信号量集 的id

第二个参数代表你要操作第几个信号量

第四个参数定义一个联合体

 

 cmd:SETVAL设置信号量的初值

 联合体的作用?

 

具体思路 :main 函数进来获取/创建一个信号量

然后对信号量初始化操作

接着我们去创建进程(代码还没写完)

 

 运行情况

 能不能让儿子先运行,父亲在运行呢?

(用到了p  v操作)

取钥匙:

第一个参数是id

第二个参数是配置的信号量(是一个指针,指向一个数组)

        第三个是第二项的个数

         

 完整的代码

思路:

main 函数调用semget 创建了一个信号量,这个信号量是一个信号量集合,只有一个信号量

再调用semclt 初始化这个信号量,只有一个信号量

信号量(当成锁)初始化为0

再用frok去创建子进程

子进程打印一句话,然后把锁放进去,也就是修改信号量

即使父亲先运行,没有”锁“,也只能卡那,必须等儿子给他,他才能运行

 

 运行的结果

 如果销毁锁呢?

调用semctl()

这个他说可以三个参数,可以四个参数,销毁的时候三个就够(这个IPC_RMID是不是似曾相识?)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值