进程间的通信
1、什么是进程间的通信?
不同进程间的消息的传播或者交换信息。
简单来说进程之间可以互相发送消息
2、进程间的通信方式
1、传统间的进程通信方式
无名管道
有名管道
信号通信
2、系统V版本引入的通信方式
消息队列
共享内存
信号灯集
一、无名管道
1、无名管道的特点
1.无名管道是内核空间实现的机制
2.只能用于亲缘间进程的通信(父子进程)
3.无名管道数据半双工的通信的方式
单工 : A -------------->B
半双工 : 同一时刻 A----->B B------>A
全双工 : 同一时刻 A<---->B
4.无名管道的大小是64K
5.无名管道不能够使用lseek函数(调用会出错 返回 -1)
6.管道中的数据被都走了就没有了
7.读写的特点
如果读端存在 写管道:有多少写多少,直到写满为止(64k)写阻塞,直到管道中腾出新的4K空间,写操作解除阻塞
如果读端不存在 写管道,管道破裂(SIGPIPE)
如果写端存在 读管道:有多少读多少,没有数据的时候阻塞等待
如果写端不存在 读管道:有多少读多少,没有数据的时候立即返回(非阻塞)
#include <unistd.h>
int pipe(int pipefd[2]);
功能:
pipe会创建一个管道,是一个单向的数据通道,可以用于进程间通信
参数:
pipefd:管道的文件描述符
pipefd[0] 是管道的读端 pipefd[1] 是管道的写端
返回值:
成功 0
失败 -1 重置错误码
#include <my_head.h>
int main(int argc, char const *argv[])
{
// 创建无名管道
int pipefd[2]={0};
int ret = 0;
ret = pipe(pipefd);
if(ret == -1){
printf("pipe error\n");
exit(-1);
}
//如果读端存在 写管道:有多少写多少,
//直到写满为止(64k)写阻塞,直到管道中腾出新的4K空间,写操作解除阻塞
int buf[65536]={0};
write(pipefd[1],buf,65536);
read(pipefd[0],buf,4096);
write(pipefd[1],"a",1);
printf("写入成功\n");
#if 0
//如果写端不存在 读管道:有多少读多少,没有数据的时候立即返回(非阻塞)
char buf[32]= "hello";
write(pipefd[1], buf, sizeof(buf));
close(pipefd[1]); //关闭写端
memset(buf,0, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf(" 第一次读取 buf =%s \n",buf);
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf(" 第二次读取 buf =%s \n",buf);
#endif
# if 0
//管道写数据
char buf[32]= "hello";
write(pipefd[1], buf, sizeof(buf));
//读取管道中的内容
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf(" 第一次读取 buf =%s \n",buf);
memset(buf, 0, sizeof(buf));
read(pipefd[0], buf, sizeof(buf));
printf(" 第二次读取 buf =%s \n",buf);
#endif
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
二、有名管道
1、特点
- 普通文件
l 链接文件
s 套接字文件
p 管道文件
b 块设备文件
c 字符设备文件
d 目录文件
有名管道会在文件系统中创建一个管道文件,只需要打开这个文件,进行读写操作即可
管道文件本质是在内存上的,在硬盘上的只是一个标识
1.可以用于任意进程间的通信,不仅限亲缘进程
2.有名管道数据是半双工的通信方式
3.有名管道的大小是64K
4.有名管道不能够使用lseek函数(调用会失败 返回 -1)
5、读写的特点
如果读端存在、写管道:有多少写多少,直到写满为止(64k)写阻塞
如果读端不存在、写管道:
1.读端没有打开,写端在open的位置阻塞
2.读端打开后关闭,管道破裂(SIGPIPE)
如果写端存在 读管道:有多少读多少,没有数据的时候阻塞等待
如果写端不存在 读管道
1.写端没有打开,读端在open的位置阻塞
2.写端打开后关闭,有多少读多少,没有数据的时候立即返回
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
参数:
pathname:管道文件的路径和名字
mode:权限 mode & ~umask --> 最终权限
返回值:
成功 0
失败 -1 重置错误码
#include <my_head.h>
int main(int argc, char const *argv[])
{
int fd = open("my_fifo", O_WRONLY);
if(-1 == fd)
exit(-1);
printf("管道文件打开成功\n");
char buf[128]={0};
while(1){
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
write(fd, buf , sizeof(buf));
if(!strcmp(buf,"quit")){
break;
}
}
close(fd);
return 0;
}
#include <my_head.h>
int main(int argc, char const *argv[])
{
int fd = open("my_fifo", O_RDONLY);
if(-1 == fd)
exit(-1);
printf("管道文件打开成功\n");
char buf[128]={0};
while(1){
memset(buf, 0, sizeof(buf));
read(fd, buf , sizeof(buf));
if(!strcmp(buf,"quit")){
break;
}
printf("读到的数据 : %s\n",buf);
}
close(fd);
return 0;
}
#include <my_head.h>
int main(int argc, const char *argv[])
{
if(-1 == mkfifo("my_fifo", 0664))
printf("mkfifo error");
return 0;
}
三、信号通信(重点)
1、信号的概念
信号是在软件层次上对中断机制的一种模拟,是一种 异步通信方式。
用户可以给进程发信号,进程可以给进程发信号,linux内核也可以给进程发信号。
如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;
2、信号的分类
在Linux中,信号被分为不可靠信号和可靠信号,一共64种,可以通过kill -l命令来查看
● 不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31
● 可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为32~64
3、信号的产生方式:
● 对于前台进程,用户可以输入特殊终端字符来发送,比如输入Ctrl+C 结束进程
● 系统异常,比如浮点异常和非法内存段访问
● 系统状态变化,比如alarm定时器到期时将引起SIGALRM信号
● 在终端运行kill命令或在程序中调用kill函数
4、信号的处理方式
信号的处理方式有三种:默认、忽略、捕捉
● 忽略信号:不做任何处理,但是对SIGKILL和SIGSTOP信号不能做忽略处理,一个杀死,一个停止
● 捕捉信号:执行自定义的信号处理函数
● 执行(缺省)默认操作:Linux系统中对每种信号规定了默认操作,即执行信号默认的功能
5、常见的信号
kill -l 查看信号
man 7 signal:
SIGKILL:结束进程,不能被忽略不能被捕捉
SIGSTOP:结束进程,不能被忽略不能被捕捉
SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程
SIGINT:结束进程,对应快捷方式ctrl+c
SIGTSTP:暂停信号,对应快捷方式ctrl+z
SIGQUIT:退出信号,对应快捷方式ctrl+\
SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
SIGCHLD 当子进程退出的时候,会给父进程发送该信号,默认的行为是忽略。
上述信号中只有 SIGKILL 和 SIGSTOP 不能被忽略和捕捉
6、信号处理的过程
程序运行在用户空间时->进程由于系统调用或中断进入内核->转向用户空间执行信号处理函数->信号处理函数完毕后进入内核->返回用户空间继续执行程序
7、信号处理函数API
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:
signum:信号的编号
除了 SIGKILL 和 SIGSTOP !!!!!
handler:信号的处理方式
SIG_IGN 忽略
SIG_DFL 默认
也可以传一个函数的首地址 捕捉
void sig_func(int signum){
//信号处理函数
}
返回值:
成功 handler的首地址
失败 SIG_ERR 重置错误码
#include <my_head.h>
void sig_func(int signum){
//信号处理函数
printf("我是杀不死的\n");
}
int main(int argc, char const *argv[])
{
#if 0
signal(SIGINT, SIG_IGN); //忽略
#endif
#if 0
signal(SIGINT, SIG_DFL); //默认
#endif
signal(SIGINT, sig_func); //捕捉
while(1){
sleep(1);
printf("我还活着\n");
}
return 0;
}
#include <signal.h>
int raise(int sig);
功能:给自己发信号
参数:sig:信号的编号
返回值:
成功 0
失败 非0
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给指定pid的进程发信号
参数:
pid:
>0 给指定pid的进程发信号 --常用
=0 发给和调用进程同组的进程
-1 给所有有权限操作的进程发信号(init 除外)
-1 > 发给所有组ID位 -pid 的进程
sig:信号的编号
返回值:
成功 0
失败 -1 重置错误码
#include <my_head.h>
int main(int argc, char const *argv[])
{
if(argc != 2)
return -1;
printf("准备 杀死自己\n");
int pid = getpid();
int signum = atoi(argv[1]+1);
kill(pid, signum);
while(1){
sleep(1);
printf("马上了 别急\n");
}
return 0;
}
#include <my_head.h>
void func(int signum){
printf("子进程退出成功\n");
raise(SIGKILL);
}
int main(int argc, char const *argv[])
{
signal(SIGCHLD, func);
pid_t pid = 0;
if(-1 == (pid = fork())){
printf("fork error\n");
return -1;
}else if(pid == 0){
printf("我是子进程,3秒退出\n");
sleep(3);
printf("我是子进程 我要退出了\n");
exit(0);
}else if(pid > 0 ){
while(1){
sleep(1);
printf("我是父进程\n");
}
}
return 0;
}
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
在设置的时间到达后,给进程发一个SIGALRM信号
如果seconds为0 则不会发出信号 且会取消之前的闹钟
重新调用alarm也会取消之前的闹钟
参数:seconds:秒数
返回值:
如果之前设置过闹钟,返回的是上一个闹钟剩余的秒数
如果之前没有设置过闹钟,返回0
#include <my_head.h>
void fun(int signum){
printf("自动出牌\n");
alarm(3);
}
int main(int argc, char const *argv[])
{
signal(SIGALRM, fun);
alarm(3);
while(1){
getchar();
printf("手动出牌\n");
alarm(3);
}
return 0;
}
四、共享内存
1、概念
在内核中创建共享内存(基于内核实现的),让进程A和进程B都能够访问到,通过这段内存进行数据的传递。
共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝)
2、函数 shared memory
1. 创建唯一key键值 ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:
获取IPC通信的键值 (//密钥)
参数:
pathname:路径和文件名(必须是已存在的 可访问的文件)
proj_id:填充的是任意一个字符,但只使用了低8bit位
返回值:
成功 key_t 键值
失败 -1 重置错误码
2. 创建或打开共享内存 shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:
创建共享内存
参数:
key:键值
size:共享内存的大小 PAGE_SIZE(4k)的整数倍
shmflg:共享的标志位
IPC_CREAT|0666
或
IPC_CREAT|IPC_EXCL|0666
返回值:
成功 共享内存编号
失败 -1 重置错误码
3. 映射共享内存 shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
映射共享内存到当前的进程空间
参数:
shmid:共享内存编号
shmaddr:属性 NULL,让系统自动分配
shmflg:共享内存操作方式
0 读写
SHM_RDONLY 只读
返回值:
成功 指向共享内存的地址//通过这个地址就可以操作共享内存了
失败 (void *)-1 重置错误码
4. 取消映射 shmdt
int shmdt(const void *shmaddr);
功能:
取消地址映射
参数:
shmaddr:指向共享内存的指针
返回值:
成功 0
失败 -1 重置错误码
5. 删除共享内存 shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
共享内存控制的函数
参数:
shmid:共享内存编号
cmd:操作的命令码
IPC_STAT:获取shmid属性信息,用到第三个参数(了解)
struct shmid_ds shd;
shmctl(shmid, IPC_STAT, &shd);
IPC_SET:设置shmid属性信息,用到第三个参数(了解)
struct shmid_ds shd;
shd.size = 100;
shmctl(shmid, IPC_SET, &shd);
IPC_RMID:删除共享内存,第三个参数填空 (重要)
shmctl(shmid, IPC_RMID,NULL);
buf:共享内存属性结构体指针
返回值:
成功 0
失败 -1 重置错误码
#include <my_head.h>
int main(int argc, char const *argv[])
{
//创建键值
key_t my_key = ftok("./09ftok.c",'a');
if(-1 == my_key)
return -1;
printf("my_key = %#x\n",my_key);
//创建或者打开共享内存
int shmid = shmget(my_key, 4096, IPC_CREAT | 0666);
if(-1 == shmid)
return -1;
printf("shmid = %d\n",shmid);
//映射到当前空间
char *p = shmat(shmid, NULL, 0);
if(p == (void *)-1)
return -1;
//读写 操作
//取消映射
if(-1 ==shmdt(p))
return 01;
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
生成思维导图
最新发布