Linux操作系统编程——进程间的通信
由于进程间空间独立,无法直接通信,所以需要IPC机制实现通信
同一主机进程间通信
1,古老的通信方式:无名管道;有名管道;信号
2,IPC对象通信system v:共享内存(效率最高);消息队列;信号量集(信号灯)
不同主机之间的通信:socket通信;网络通信
通信类型
单工:数据传输是完全单向的,通信双方严格分工,一方固定为发送端(只能发不能收),另一方固定为接收端(只能收不能发),传输方向不可改变,例如广播
半双工:数据传输允许双向进行,但同一时刻只能单向传输 ,发送和接收功能不能同时开展,需交替进行,例如对讲机
全双工:数据传输可同时双向进行,发送端和接收端能独立收发数据,无需等待切换例如打电话
古老方式
管道
有名管道:可以用于同一主机任意进程间通信
无名管道:只能用于同一主机具有亲缘关系的进程通信(父子进程之间)
无名管道
本质:内核空间中的一段缓冲区,遵循先进先出特性
默认大小:64K
特性:
1,写堵塞:读端和写端都存在时,向管道中写数据,当管道满时,发生写阻塞
2,读阻塞:读端和写端都存在时,向管道中读数据,若管道为空,发生读阻塞
3,读返回0:当写端关闭,从管道中读数据,若有数据则读到数据,若没有数据则read返回0,不再阻塞
4,管道破裂:读端关闭,向管道中写入数据。这是一种异常,操作系统会强制让进程结束
操作流程:
1)创建无名管道:pipe()
2)写管道:write()
3)读管道:read()
4)关闭管道:close()
不必刻意释放,因为属于内核区域,关闭会自动释放
pipe
参数:
pipefd[0]为读端
pipefd[1]为写端
返回值:成功则0失败-1
管道文件应该在fork()之前创建。读写端不能交换
memset()
功能:将内存区域填充成指定数据
参数:
s:要填充的空间首地址
c:要填充的字符
n:要填充的字节数
返回值:成功则为s的首地址,失败则为NULL
一般情况下,不会让一个进程既可写又可读,保证管道的方向性,如果要两个进程互相通信,需要构建两个管道
有名管道
本质:内核空间的一段缓冲区,这段缓冲区和一个管道文件相关联
操作流程:
1,创建管道文件:mkififo()
2,打开管道文件:open()
3,读或写管道文件:write()
4,关闭管道文件:close()
5,删除管道文件:remove()
mkfifo()
功能:创建管道文件
参数:管道文件的名称;管道文件的读写执行权限
返回值:成功为0失败-1
open会有阻塞的效果,当以只读的方式打开时,会阻塞直到有进程以写的方式打开,对于只写方式同理
效果是向一个进程输入字符串,另一个进程打印出来
信号
实现进程间通知机制,实现进程间的异步通信(接收方不知道什么时候发送方会发送时数据),是一种软中断
分类
系统支持的信号:
1,kill -l
2)SIGINT(crtl + c)让一个进程被打断
3)SIGQUIT(ctrl + \)让一个进程结束
9)SIGKILL(管理员信号):强制让一个进程结束
11)SIGSEGV:让一个进程结束(段错误)
13)SIGIPIPE:让一个进程结束(管道破裂)
14)SIGALRM:让一个进程结束(定时时间到达)
17)SIGCHLD:子进程结束时发送给父进程
18)SIGCONT:让停止态的进程继续运行
19)SIGSTOP(管理员信号):让运行态的进程进入停止态(暂停)强制停止
20)SIGTSTP(ctrl + z):让后台进程进入暂停态,时来自终端的停止信号
2,信号处理流程
3,信号处理方式
1)缺省:按照默认处理方式
2)忽略:不处理
3)捕获:以自定义方式处理
值得注意的是,管理员信号无法被忽略和捕获,即9和19只能按照默认方式处理
signal
功能:设置信号的处理方式(注册一个信号)
参数:
signum:要处理的信号的编号
handle:
SIG_IGN:以忽略的方式处理该信号,即不处理
SIG_DFL:以缺省的方式处理,即系统默认方式
函数的地址:以捕获方式处理,即自定义
返回值:失败则为NULL;自定义方式时,返回函数的地址
注意:
1,若信号不被注册,则按默认方式处理
2,信号只需注册一次
3,每次信号的到来,都会触发一次信号处理函数
4,信号尽可能早注册
如图,signal按照handler的方式运行,所以此时SIGINT信号并不能打断该进程,即按照系统默认的方式运行
发送信号
1,kill命令
2,kill()
功能:给指定的进程发送一个信号
参数:接收信号的进程的pid;信号的编号
返回值:成功则0失败-1
3,子进程结束时,会发送SIGCHLD(17)信号给父进程
子进程空间异步回收:通过子进程发送的SIGCGLD信号实现
4,raise():给自己所在的进程发送信号
5,alarm()
功能:设置一个闹钟,闹钟时间到达时,向进程发送一个SIGALRM的信号
参数:秒数
返回值:成功则为上次设定剩余的时间,失败则为0
注:如果定了两个,则第一个会失去效果
6,pause():让一个进程进入到睡眠状态
注:pause可以被一个可以被捕获的信号唤醒
IPC
可以通过ipcs -a查看内核终端IPC对象,-s为删除信号量集,-m+id为删除共享内存
共享内存
使用内核空间中的内存区域共享数据,采用内存映射技术,减少的数据的反复拷贝,提高了通信效率,本质是通过MMU(内存管理单元)的管理将不同的内存空间映射到同一物理内存区域中
操作流程:
1)创建一个IPC key:key_t ftok(const char *pathname, int proj_id)
2)创建共享内存:shmget
3)建立共享内存段和用户空间的内存映射:shmat
4)向共享内存写入数据:通过用户空间首地址
5)解除映射关系:shmdt
6)删除共享内存:shmctl
ftok
功能:创建一个IPC key
参数:
pathname:路径,随便写,只需在两个进程之间创建key时传递的路径相同
proj_id:工程ID,与其他key不冲突,一般来说会用特殊的字符表示,例如'!'
返回值:成功则为IPC key,失败则为-1
注意:两个进程在创建KEY时,必须使用相同的参数,
shmget
功能:创建一个共享内存
参数:
key:IPC key
size:共享内存大小,会被扩展成PAGE_SIZE(4096byte)的整数倍
shmflg:IPC_CREAT|0664(创建并给予权限)
大小不超过1G,因为内核大小只有1G,而且一般情况下共享内存最大500M
返回值:成功则为共享内存的ID,失败则为-1
shmat
功能:建立共享内存映射
参数:
shmid:共享内存的id
shmaddr:映射的用户空间首地址,如果是NULL则是让操作系统分配
shmflg:
SHM_RDONLY:只读
!SHM_RDONLY:可读可写
返回值:成功则为映射的用户空间首地址,失败则为(void *)-1
shmdt
功能:解除内存映射关系
参数:要解除的用户空间首地址
返回值:成功为0失败-1
shmctl
功能:操作共享内存
参数:
shmid:要操作的共享内存id
cmd:要执行的操作指令
IPC_RMID:删除操作
buf:设置的参数(删除的时候不需要)
返回值:成功为0失败-1
信号量集
实现进程间同步
消息队列
和管道类似,有同步的效果,相对于管道而已增加了数据的等级,可以优先处理优先级较高的数据