目录
1、进程间通信常用的几种方式 :
1,管道通信:有名管道,无名管道2,信号- 系统开销小3,消息队列-内核的链表4,信号量-计数器5,共享内存6,内存映射7,套接字
2.无名管道:
1.管道的概念:
本质:
.内核缓冲区
.伪文件-不占用磁盘空间
特点:
两部分:
读端,写端,对应两个文件描述符
数据写端流入,读端流出
操作管理的进程被销毁之后,管道自动被释放
管道默认是阻塞的
2.管道的原理 :
内部实现方式:队列
环形队列
特点:先进先出
缓冲区大小:
默认4K
大小可根据实际情况做适当调整
3.管道的局限性:
队列:
数据只能读取一次,不能重复读取
4.创建匿名管道:
int pipe(int fd[2])fd‐传出参数:fd[0]‐读端fd[1]‐写端返回值:0:成功‐1:创建失败
5、demo:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int ret;
int fd[2];
ret=pipe(fd);
if(ret == -1)
{
printf("create pipe failed!\n);
exit(1);
}
printf("pipe[0] is %d\n",fd[0]); //fd[0]=3
printf("pipe[1] is %d\n",fd[1]); //fd[1]=4
close(fd[0]); //关闭读端
close(fd[1]); //关闭写端
return 0;
}
6.父子进程使用管道通信 :
(1)ps aux| grep "bash"
(2)数据重定向:
int dup2(int oldfd, int newfd);
7.管道的读写行为:
(1)读操作:
有数据:read(fd[1]) 正常读,返回读出的字节数
无数据:
1、 写端被全部关闭,read返回
0
,相当于读文件到了尾部
2、没有全部关闭
3、read阻塞
(2)写操作:
1、 读端全部关闭
管道破裂,进程被终止
内核给当前进程发送信号SIGPIPE-13,默认处理动作
2、 读端没全部关闭
缓冲区写满了
write阻塞
缓冲区没满
write继续写,直到写满,阻塞
(3)如何设置非阻塞?
默认读写两端都阻塞
1、设置读端为非阻塞pipe(fd)
fcntl-
变参函数
2、复制文件描述符-dup
3、修改文件属性-open
的时候对应
flag
属性
设置方法:
//获取原来的flagsint flags = fcntl(fd[0],F+GETFL);//设置新的flagsflag |=O_NONBLOCK;fcntl(fd[0],F_SETFL,flags);fcntl(fd[0],F_SETFL,flags);
8.查看管道缓冲区大小
1、命令 :ulimit -a
2、函数: long fpathconf(int fd, int name);
3.有名管道
函数形式:int mkfifo(const char \*filename,mode_t mode);
功能:创建管道文件参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。返回值:创建成功返回0,创建失败返回-1。
(1)特点
有名管道
在磁盘上有这样一个文件
ls -l ->p
也是一个伪文件,在磁盘大小永久为
0
数据存在内核中有一个对应的缓冲区
半双工通信方式
(2使用场景:
没有血缘关系的进程间通信
(3)创建方式
命令:
mkfifo
管道名
函数: mkfifo ----- make FIFOs (named pipes)
(4)fifo
文件可以使用
io
函数进程操作
>>open/close
>>read/write
>>不能执行
lseek
操作
4、消息队列
消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列 ID )来标识。
![](https://img-blog.csdnimg.cn/3298a878a2ba4bceb3a9ed318e9ffa75.png)
1.特点
(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
(2)消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
(3)消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。
2.相关函数
1.int msgget(key_t key, int msgflg);//创建或打开消息队列,参数:key:和消息队列关联的key值msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。返回值:成功返回队列ID,失败则返回‐1
2.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//读取消息,成功返回消息数据的长度,失败返回‐1参数:msgid:消息队列的IDmsgp:指向消息的指针,常用结构体msgbuf如下:struct msgbuf{long mtype; //消息类型char mtext[N]; //消息正文}size:发送的消息正文你的字节数flag:IPC_NOWAIT :消息没有发送完成函数也会立即返回0:知道发送完成函数才返回返回值:成功:0失败:‐1
3.ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//从一个消息队列中获取消息参数:msgid:消息队列的IDmsgp:要接收消息的缓冲区size:要接收的消息的字节数msgtype:0:接收消息队列中第一个消息大于0:接收消息队列中第一个类型为msgtyp的消息小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。flag:0:若无消息函数一直阻塞IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。返回值:成功:接收到的消息i长度出错:‐1
4.int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列,成功返回0,失败返回‐1参数:msqid:消息队列的队列IDcmd:IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆 盖msgid_ds的值。IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值IPC_RMID:删除消息队列buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构返回值:成功:0失败:‐1
key_t ftok( char * fname, int id )//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。参数:fname就时你指定的文件名(该文件必须是存在而且可以访问的)。id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。返回值:当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。
在以下两种情况下,msgget将创建一个新的消息队列:
(1)
如果没有与键值
key
相对应的消息队列,并且
flflag
中包含了
IPC_CREAT
标志
(2)
key
参数为
IPC_PRIVATE
练习:用父子进程实现消息队列的全双工通信
vim msg_service.c
#include <sys/types.h>
#include<sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
struct msgbuf
{
long mtype; //消息类型
char mtest[128]; //正文
char ID[4];
};
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
int readret;
key_t key;
key=ftok("a.c",1);
pid_t pid;
msgid =msgget( key,IPC_CREAT | 0755);
if( msgid == -1)
{
printf("create message queue failed !\n");
return -1;
}
system("ipcs -q");
printf("create message queue success magid =%d\n",msgid);
//init msgbuf
sendbuf.mtype=100;
pid=fork();
//parent progess write 100,父进程写
if( pid > 0)
{
while(1)
{
/*
# include <string.h>
void *memset(void *s, int c, unsigned long n);
将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
memset() 函数可以说是初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。
*/
memset(sendbuf.mtest,0,128);
printf("please input to message queue :\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *) &sendbuf,strlen(sendbuf.mtest),0);
}
}
// child progess read 200,子进程读
if( pid == 0)
{
while(1)
{
memset(readbuf.mtest,0,128);
//从消息队列获取消息
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//返回接收到消息的长度
readret=msgrcv(msgid,(void *)&readbuf,128,200,0);
printf(" receive byte from message queue is :%s\n",readbuf.mtest);
}
}
return 0;
}
vim msg_client.c
#include <sys/types.h>
#include<sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
struct msgbuf
{
long mtype; //消息类型
char mtest[128]; //正文
char ID[4];
};
int main()
{
struct msgbuf sendbuf,readbuf;
int msgid;
int readret;
key_t key;
key=ftok("a.c",1);
pid_t pid;
msgid =msgget( key,IPC_CREAT | 0755);
if( msgid == -1)
{
printf("create message queue failed !\n");
return -1;
}
system("ipcs -q");
printf("create message queue success magid =%d\n",msgid);
//init msgbuf
sendbuf.mtype=200;
pid =fork();
//child progess write 200
if( pid == 0)
{
while(1)
{
memset(sendbuf.mtest,0,128);
printf("please input to message queue :\n");
fgets(sendbuf.mtest,128,stdin);
msgsnd(msgid,(void *) &sendbuf,strlen(sendbuf.mtest),0);
}
}
// parent progess read 100
if( pid > 0)
{
while(1)
{
memset(readbuf.mtest,0,128);
readret=msgrcv(msgid,(void *)&readbuf,128,100,0);
printf(" receive byte from message queue is :%s\n",readbuf.mtest);
}
}
return 0;
}
执行效果:
![](https://img-blog.csdnimg.cn/b7dae44037a540799f62e558c47b18f6.png)
注意:使用 ipcs -q查看消息队列时:used-bytes 值为0,内容被读走了,相当于删除了内容,但节点还存在
5.共享内存
1.概念
共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最 高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自 己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。
2.相关函数
1.int shmget(key_t key, size_t size, int shmflg);//用来获取或创建共享内存参数:key:IPC_PRIVATE 或 ftok的返回值size:共享内存区大小shmflg:同open函数的权限位,也可以用8进制表示法返回值:成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符出错:‐1
2.void *shmat(int shm_id, const void *shm_addr, int shmflg);//把共享内存连接映射到当前进程的地址空间参数:shm_id:ID号shm_addr:映射到的地址,NULL为系统自动完成的映射shmflg:SHM_RDONLY共享内存只读默认是0,表示共享内存可读写返回值:成功:映射后的地址失败:NULL
3.int shmdt(const void *shmaddr);//将进程里的地址映射删除参数:shmid:要操作的共享内存标识符返回值:成功:0出错:‐1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);//删除共享内存对象参数:shm_id:要操作的共享内存标识符cmd :PC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐mIPC_SET (设置对象属性)IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐mbuf :指定IPC_STAT/IPC_SET时用以保存/设置属性返回值:成功:0出错:‐1
特点:
1.共享内存创建之后,一直存在于内核中,直到被删除或系统关闭
2.共享内存和管道不一样,读取后,内容仍然在共享内存中
练习:实现共享内存间的通信
vim shm_write.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
int shmid;
int key;
char *p;
key=ftok("a.c",1);
if(key < 0)
{
printf("ftok failed !\n");
return -2;
}
printf("ftok success key :%x\n",key);
shmid =shmget(key,128,IPC_CREAT | 0777);
if (shmid < 0)
{
printf("create share memory failed !\n");
return -1;
}
printf("create share memory success shmid =%d\n",shmid);
//在终端查看内存
system("ipcs -m");
//连接
p=(char *) shmat(shmid,NULL,0);
if( p== NULL)
{
printf("shmat funtion failed!\n");
return -3;
}
printf("please input to share memory:\n");
//write to share memory
fgets(p,128,stdin);
sleep(3); //延时3s
shmdt(p); //断开连接
shmctl(shmid,IPC_RMID,0); //删除
return 0;
}
vim shm_read.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
int shmid;
int key;
char *p;
key=ftok("a.c",1);
if(key < 0)
{
printf("ftok failed !\n");
return -2;
}
printf("ftok success key :%x\n",key);
//共用同一个key值
shmid =shmget(key,128,0);
if (shmid < 0)
{
printf("creat share memory failed !\n");
return -1;
}
printf("create share memory success shmid =%d\n",shmid);
//在终端查看内存
system("ipcs -m");
p=(char *) shmat(shmid,NULL,0);
if( p== NULL)
{
printf("shmat funtion failed!\n");
return -3;
}
printf("share memory data: %s\n",p);
shmdt(p);
return 0;
}
执行效果:
6.信号:
信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。
内核可以发送多少种信号呢?
kill ‐l
信号通信的框架:
信号的发送(发送信号进程):kill、raise、alarm
信号的接收(接收信号进程) : pause()、 sleep、 while(1)
信号的处理(接收信号进程) :signal
1.信号的发送(发送信号进程)
kill:
#include<signal.h>#include<sys/types.h>函数原型: int kill(pid_t pid, int sig);参数:函数传入值:pid正数:要接收信号的进程的进程号0:信号被发送到所有和pid进程在同一个进程组的进程‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外 )sig:信号函数返回值:成功 0 出错 ‐1
raise:
发信号给自己
== kill(getpid(), sig)
所需头文件 :#include<signal.h>#include<sys/types.h>函数原型:int raise(int sig);参数:函数传入值: sig :信号函数返回值:成功 0 出错 ‐1
alarm :
发送闹钟信号的函数 :
alarm 与 raise 函数的比较:
相同点:让内核发送信号给当前进程
不同点:
alarm 只会发送SIGALARM信号
alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号
所需头文件 #include <unistd.h>函数原型 unsigned int alarm(unsigned int seconds)参数:seconds:指定秒数返回值:成功:如果调用此 alarm() 前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余时间,否则返回 0 。出错: ‐1
![](https://img-blog.csdnimg.cn/a5866737c54a4990a0ff2c3f17a2140e.png)
2.信号的接收
接收信号的进程,要有什么条件:
要想使接收的进程能收到信号,这个进程不能结束
pause:进程状态为sleep
函数原型 int pause(void);函数返回值 成功: 0 ,出错: ‐1
3.信号的处理
收到信号的进程,应该怎样处理? 处理的方式:
1.进程的默认处理方式(内核为用户进程设置的默认处理方式)
(1)
:忽略
(2)
:终止进程
(3):
暂停
2.
自己的处理方式:
自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式
所需头文件 #include <signal.h>函数原型 void (*signal(int signum, void (*handler)(int)))(int);函数传入值signum:指定信号handler:SIG_IGN:忽略该信号。SIG_DFL:采用系统默认方式处理信号自定义的信号处理函数指针函数返回值成功:设置之前的信号处理方式出错:‐1signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函 数;这个函数的返回值是一个函数指针。
练习:
vim kill.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc , char *argv[])
{
int sig,pid;
if(argc <3 )
{
printf(" input error!\n");
return -1;
}
//因为输入的是字符,把字符转换成整型
sig =atoi(argv[[1]);
pid =atoi(argv[2]);
printf("sig = %d,pid =%d\n",sig,pid);
//发送信号进程
kill(pid,sig);
return 0;
}
使用:运行一个进程,然后执行./kill 9 pid就可以把刚才运行的进程杀死
vim raise.c
#include <stdio.h>
#include <signal.h>
int main()
{
pid_t pid;
pid =fork();
if( pid > 0)
{
sleep(8);
if(waitpid(pid,NULL,WNOHANG) == 0)
{
kill(pid,9);
}
wait(NULL);
while(1);
}
if( pid == 0)
{
printf("before sig:\n");
raise(SIGTSTP);
printf("after sig \n");
}
return 0;
}
vim alarm.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int i=0;
printf("before alarm \n");
alarm(7);
//pause();
printf("after alarm \n");
while(i<20)
{
i++;
sleep(1);
printf("progess %d\n",i);
}
return 0;
}
vim signal.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <signal.h>
void myfun(int signum)
{
int i=0;
while( i<10)
{
printf("progess signum = %d i= %d\n",signum,i);
sleep(1);
i++;
}
int main()
{
int i=0;
//14:SIGALRM
signal(14,myfun);
printf("before alarm \n");
alarm(7);
//pause();
printf("after alarm \n");
while(i<12)
{
i++;
sleep(1);
printf("progess %d\n",i);
}
return 0;
}
7.信号灯
信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)
函数原型: int semget(key_t key, int nsems, int semflg);//创建一个新的信号量或获取一个已经存在的信号量的键值。所需头文件:#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>函数参数:key:和信号灯集关联的key值nsems: 信号灯集中包含的信号灯数目semflg:信号灯集的访问权限函数返回值:成功:信号灯集ID出错:‐1
函数原型:int semctl ( int semid, int semnum, int cmd,…union semun arg(不是地址));//控制信号量,删除信号量或初始化信号量所需头文件:#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>函数参数:semid:信号灯集IDsemnum: 要修改的信号灯编号cmd :GETVAL:获取信号灯的值SETVAL:设置信号灯的值IPC_RMID:从系统中删除信号灯集合函数返回值:成功:0出错:‐1
int semop(int semid ,struct sembuf *sops ,size_t nsops);//用户改变信号量的值。也就是使用资源还是释放资源使用权包含头文件:include<sys/sem.h>参数:semid : 信号量的标识码。也就是semget()的返回值sops是一个指向结构体数组的指针。struct sembuf{unsigned short sem_num;//信号灯编号;short sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作short sem_flg;0阻塞,1非阻塞};sem_op : 操作信号灯的个数(1)如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;(2)如果sem_op的值为负 数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用 权;(3)如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。