一、Xmind整理:
消息队列的原理:
共享内存的原理:
二、课上练习:
练习1:用信号的方式回收僵尸进程(重点!)
1.子进程退出后,父进程会收到17)SIGCHLD信号。
2.父进程中捕获17)SIGCHLD信号,给该信号注册新的处理函数。在该新的处理函数中执行waitpid函数,回收僵尸进程。
3.当在信号A的处理函数内部时,再次触发A信号,会导致信号屏蔽,会造成多个子进程短时间内同时退出,父进程只会处理一个17号信号,导致僵尸进程收不干净的问题。
4.解决方式:当成功回收到僵尸进程后,再回收一次。直到没有僵尸进程,结束循环。即判断waitpid(-1, NULL, WNOHANG);的返回值
① =0,有子进程,但是没有僵尸进程
② =-1,没有子进程,也没有僵尸进程
小练1:回收一个子进程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <head.h>
void handler(int sig)
{
printf("触发%d信号\n",sig);
pid_t wpid =waitpid(-1,NULL,WNOHANG);
printf("wpid =%d\n",wpid);
return;
}
int main(int argc, const char *argv[])
{
pid_t cpid = fork();
if(cpid > 0)
{
//捕获2号SIGINT信号
if(signal(17,handler) == SIG_ERR)
{
perror("signal");
return -1;
}
printf("捕获17号信号成功\n");
while(1)
{
printf("this id parent: %d %d\n",getpid(),cpid);
sleep(1);
}
}
else if(0 == cpid)
{
for(int i = 0; i<3; i++)
{
printf("this is cpid %d %d\n",getppid(),getpid());
sleep(1);
}
printf("子进程 %d 准备退出\n",getpid());
exit(0);
}
else
{
perror("fork");
return -1;
}
return 0;
}
小练2:多个子进程短时间内同时退出问题
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <head.h>
#include <sys/wait.h>
int count = 0;
void handler(int sig)
{
while(1)
{ //当回收成功后,再收一次,直到回收失败
//=-1,没有僵尸进程,也没有子进程
//=0,没有僵尸进程,但是有子进程
pid_t wpid = waitpid(-1,NULL,WNOHANG);
if(wpid <= 0)
break;
printf("[%d] wpid = %d\n",++count,wpid);
}
/*
也可以这样写
while(waitpid(-1,NULL,WNOHANG) > 0);
*/
return;
}
int main(int argc, const char *argv[])
{
//捕获17号SIGINT信号
if(signal(17,handler) == SIG_ERR)
{
perror("signal");
return -1;
}
printf("捕获17号信号成功\n");
int i = 0;
while(i < 100)
{
if(fork() == 0) //若是子进程
{
exit(0); //退出
}
i++; //只有父进程执行i++
}
//能运行到当前位置,则代表是父进程
while(1)
sleep(1);
return 0;
}
练习2:kill
功能:给指定进程或者进程组发送一个信号
原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid_t pid:指定要发送的进程或者进程组;
pid > 0, 将信号发送给指定的进程,进程号==pid参数;
pid == 0, 将信号发送给当前进程组下的所有进程;
pid == -1, 将信号发送给当前进程权限所能送达的所有进程,除了1号进程;
pid < -1, 将信号发送给指定进程组下的所有进程。进程组ID == -pid参数;
int sig:指定要发送的信号的编号,可以填对应的宏;
sig==0代表没有信号被发送,但是依然会检测对方进程是否存在,或者是否有权限访问。
返回值:
成功,返回0;
失败,返回-1,更新errno;
练习3:alarm
功能:设置一个定时器,当时间到后,给当前进程发送一个14) SIGALRM 信号
原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:
unsigned int seconds:设置定时器时间,以秒为单位;
seconds == 0, 则取消定时器;
返回值:
>0, 返回上一个定时器没有走完的时间;
=0, 没有未走完的定时器;
小练: 设置一个3s的定时器
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void callback(int sig)
{
printf("alarm...\n");
alarm(3); //设置一个3s的定时器
return;
}
int main(int argc, const char *argv[])
{
//捕获14号信号
if(signal(14,callback) == SIG_ERR)
{
perror("signal");
return -1;
}
alarm(3); //设置一个3s的定时器
while(1)
{
printf("signal...\n");
sleep(1);
}
return 0;
}
练习4:ftok
功能:该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用。只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
char *pathname:文件的路径以及名字; 该文件必须存在且可访问
int proj_id:传入一个非0参数;
返回值:
成功,返回计算得到的key值;
失败,返回-1,更新errno;
小练: 创建消息队列
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc, const char *argv[])
{
//创建key值
key_t key =ftok("/",1);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n",key);
//创建消息队列
int msqid = msgget(key,IPC_CREAT|0664);
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n",msqid);
system("ipcs -q"); //让代码执行shell指令
return 0;
}
练习5:msgget
功能:通过key值到内核内存中找对应的消息队列,并返回消息队列的id ---> msqid
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:
key_t key:ftok函数返回出来的key值;
int msgflg:
IPC_CREAT:若消息队列不存在,则创建消息队列。若消息队列存在,则忽略该选项;
IPC_CREAT|0664:创建的同时指定消息队列的权限。
IPC_CREAT|IPC_EXCL:若消息队列不存在,则创建消息队列。若消息队列存在,则报错;
返回值:
>=0, 成功返回消息队列的id号 msqid;
=-1, 函数运行失败,更新errno;
练习6:msgsnd
功能:将数据打包后发送到消息队列中
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
int msqid:指定要发送到哪个消息队列中;
void *msgp:指定要发送的消息包的首地址; 通用格式如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */ 消息类型,必须大于0;
char mtext[1]; /* message data */ 消息内容,类型根据需求修改,想要发什么类型就填什么类型。
大小与下一个参数msgsz指定的一致
};
size_t msgsz:消息内容的大小,以字节为单位。
int msgflg:
0:阻塞方式发送,当消息队列满了,则当前函数阻塞;
IPC_NOWAIT:非阻塞方式,当消息队列满了,该函数不阻塞,且函数运行失败,errno == EAGAIN.
返回值:
=0, 函数运行成功;
=-1, 函数运行失败,更新errno;
小练:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main(int argc, const char *argv[])
{
//创建key值
key_t key =ftok("/",1);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n",key);
//创建消息队列
int msqid = msgget(key,IPC_CREAT|0664);
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n",msqid);
struct msgbuf sndbuf;
while(1)
{
printf("请输入消息类型:");
scanf("%ld",&sndbuf.mtype);
getchar();
if(0 == sndbuf.mtype) //若终端输入0,则跳出循环
break;
printf("请输入消息内容:");
fgets(sndbuf.mtext,sizeof(sndbuf.mtext),stdin);
sndbuf.mtext[strlen(sndbuf.mtext)-1] = 0;
//向消息队列中发送数据
if(msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtext),0)< 0)
{
perror("msgsnd");
return -1;
}
printf("发送成功\n");
system("ipcs -q"); //让代码执行shell指令
}
return 0;
}
练习7:msgrcv
功能:从消息队列中读取数据
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数:
int msqid: 指定要从哪个消息队列中读取消息;
void *msgp:指定将读取到的数据存储到什么位置,填对应空间的地址; 以什么形式写入,就以什么形式读取。通用格式如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */ 消息类型,必须大于0;
char mtext[1]; /* message data */ 消息内容,类型根据需求修改,想要发什么类型就填什么类型。
大小与下一个参数msgsz指定的一致
};
size_t msgsz:指定要读取多少个字节大小的消息内容,以字节为单位。
long msgtyp:指定要读取的消息类型;
msgtyp == 0, 读取消息队列中的第一条消息; 先进先出;
msgtyp > 0, 指定消息类型读取,读取消息队列中第一条消息类型为 msgtyp参数指定的消息;
msgflg指定了MSG_EXCEPT,读取消息队列中第一条消息类型 不等于 msgtyp参数指定的消息;
vi -t MSG_EXCEPT; #define MSG_EXCEPT 020000
msgtyp < 0, 读取消息队列中第一条最小的,且类型小于等于 msgtyp参数绝对值的消息。
int msgflg:
0:阻塞方式,当消息队列中没有消息了,该函数阻塞;
IPC_NOWAIT:非阻塞方式运行,当消息队列中没有消息了,该函数不阻塞,运行失败,errno == ENOMSG;
返回值:
>0, 成功读取到的字节数;
=-1, 函数运行失败,更新errno;
代码示例:
若消息队列中有消息:
mtype 100 101 99 100 101
mtext aaa bbb ccc ddd eee
i. msgtyp == 0
while(1)
{
//阻塞方式读取消息队列中第一条消息,先进先出的原则
//res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, 0);
//非阻塞方式读取消息队列中第一条消息,先进先出的原则
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, IPC_NOWAIT);
if(res < 0)
{
perror("msgrcv");
return -1;
}
printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
}
输出顺序:
100 aaa 101 bbb 99 ccc 100 ddd 101 eee
ii. msgtyp > 0
while(1)
{
//1.阻塞方式读取消息队列中第一条消息类型 == 101 的消息
//res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, 0);
//2.非阻塞方式读取消息队列中第一条消息 == 101 的消息
//res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT);
//3.非阻塞方式读取消息队列中第一条消息类型 != 101的消息
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT|020000);
if(res < 0)
{
perror("msgrcv");
return -1;
}
printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
}
注释1,2的现象:
101 bbb 101 eee
第3个的现象:
100 aaa 99 ccc 100 ddd
iii.msgtyp < 0
while(1)
{
//阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, 0);
//非阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
//res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, IPC_NOWAIT);
if(res < 0)
{
perror("msgrcv");
return -1;
}
printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
}
现象:
res=128 : 99 ccc
res=128 : 100 aaa
res=128 : 100 ddd
练习8:msgctl
功能:控制消息队列,常用于删除消息队列
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
int msqid:指定要控制的消息队列的id号;
int cmd:
IPC_STAT:获取消息队列的属性,属性存储在第三个参数中;
IPC_SET:设置消息队列属性,属性存储在第三个参数中;
IPC_RMID:删除消息队列,第三个参数无效,填NULL即可;
返回值:
成功,返回0;
失败,返回-1,更新errno;
//删除消息队列
if(msgctl(msqid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
printf("删除消息队列成功\n");
练习9:共享内存函数 ftok
功能:该函数通过pathname提供的id,以及proj_id提供的8bit的值,计算key值(键值),给msgget shmget semget函数使用。只要pathname和proj_id一致,则计算的key值就一致。那么通过相同key值找到的IPC对象就是同一个。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数:
char *pathname:文件的路径以及名字; 该文件必须存在且可访问
int proj_id:传入一个非0参数;
返回值:
成功,返回计算得到的key值;
失败,返回-1,更新errno;
练习10:shmget
功能:通过key值到内核内存中找对应的共享内存,并返回共享内存的id ---> shmid
原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key_t key:ftok函数返回出来的key值;
size_t size:指定要申请多少个字节的共享内存;
int shmflg:
IPC_CREAT:若消息队列不存在,则创建共享内存。若共享内存存在,则忽略该选项;
IPC_CREAT|0664:创建的同时指定共享内存的权限。
IPC_CREAT|IPC_EXCL:若共享内存不存在,则创建共享内存。若共享内存存在,则报错;
返回值:
>=0, 成功返回共享内存的id号 shmid;
=-1, 函数运行失败,更新errno;
练习11:shmat
功能:将共享内存映射到用户空间中
原型:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
int shmid:指定要映射的共享内存id号 ,
const void *shmaddr:指定共享内存要映射到用户空间的位置,填对应空间的首地址; 例如:(void*)0x10
填NULL,代表让操作系统自动映射;
int shmflg :
0:默认方式映射,进程对共享内存可读可写;
SHM_RDONLY:只读,进程对共享内存只读;
返回值:
成功,返回共享内存映射到用户空间的首地址;
失败,返回 (void *) -1,更新errno;
注意:获取到的映射空间的首地址的指向不允许修改,若修改后会导致首地址找不到,导致内存泄漏,与堆空间首地址不能改变的概念一致
小练:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <head.h>
#include <sys/ipc.h>
int main(int argc, const char *argv[])
{
//创建key值
key_t key = ftok("./",10);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n",key);
//创建共享内存,获得shmid号
int shmid =shmget(key, 32, IPC_CREAT|0664);
if(shmid < 0)
{
perror("shmget");
return -1;
}
printf("shmid = %d\n",shmid);
//映射共享内存到用户空间
void* addr = shmat(shmid,NULL,0);
if((void*)-1 == addr)
{
perror("shmat");
return -1;
}
printf("addr = %p\n",addr);
system("ipcs -m");
return 0;
}
练习12:shmdt
功能:将共享内存与进程的用户空间断开映射; 当进程不想操作共享内存的时候,就可以断开映射
原型:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
void *shmaddr:指定要断开映射的用户空间的首地址
返回值:
成功,返回0;
失败,返回-1,更新errno;
练习13:shmctl
功能:控制共享内存,常用于删除共享内存
原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
int shmid:指定要控制的共享内存;
int cmd:
IPC_STAT:获取共享内存的属性,属性存储在第三个参数中;
IPC_SET:设置共享内存属性,属性存储在第三个参数中;
IPC_RMID:删除共享内存,第三个参数无效,填NULL即可;
返回值:
成功,返回0;
失败,返回-1,更新errno;
//删除共享内存
if(shmctl(shmid, IPC_RMID, NULL) < 0)
{
perror("shmctl");
return -1;
}
printf("删除共享内存成功\n");
三、课后作业:
1.要求用消息队列实现AB进程对话
A进程先发送一句话给B进程,B进程接收后打印
B进程再回复一句话给A进程,A进程接收后打印
重复1.2步骤,当收到quit后,要结束AB进程
实现随时收发:用多进程 多线程。
A进程:
#include <stdio.h>
#include <string.h>
#include <head.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main(int argc, const char *argv[])
{
//创建key值
key_t key =ftok("./",1);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n",key);
//创建消息队列
int msqid = msgget(key,IPC_CREAT|0664);
if(msqid < 0)
{
perror("msgget");
return -1;
}
printf("msqid = %d\n",msqid);
struct msgbuf sndbuf;
//创建一个子进程
pid_t cpid = fork();
if(cpid > 0)
{
while(1)
{
sndbuf.mtype = 1;
scanf("%s",sndbuf.mtext);
msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtype),0);
if(strcmp(sndbuf.mtext,"quit") == 0)
break;
}
}
else if(0 == cpid)
{
while(1)
{
msgrcv(msqid,&sndbuf,sizeof(sndbuf.mtext),2,0);
if(strcmp(sndbuf.mtext,"quit") == 0)
break;
printf("%s\n",sndbuf.mtext);
}
kill(getppid(),2);
}
else
{
perror("fork");
return -1;
}
return 0;
}
B进程:
#include <stdio.h>
#include <string.h>
#include <head.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main(int argc, const char *argv[])
{
key_t key = ftok("./",1);
if(ftok < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n",key);
int msqid = msgget(key,IPC_CREAT|0664);
if(msqid < 0)
{
perror("msgget");
return -1;
}
struct msgbuf sndbuf;
pid_t cpid = fork();
if(cpid > 0)
{
while(1)
{
sndbuf.mtype = 2;
scanf("%s",sndbuf.mtext);
msgsnd(msqid,&sndbuf,sizeof(sndbuf.mtype),0);
if(strcmp(sndbuf.mtext,"quit") == 0)
break;
}
}
else if(0 == cpid)
{
while(1)
{
msgrcv(msqid,&sndbuf,sizeof(sndbuf.mtext),1,0);
if(strcmp(sndbuf.mtext,"quit") == 0)
break;
printf("%s\n",sndbuf.mtext);
}
kill(getppid(),2);
}
else
{
perror("fork");
return -1;
}
return 0;
}
2.要求在共享内存中存入字符串 “1234567”。A进程循环打印字符串,B进程循环倒置字符串,要求结果不允许出现乱序:
提示:共享内存中存储 flag + string.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <sys/shm.h>
int main(int argc, const char *argv[])
{
//创建key值
key_t key = ftok("./", 3);
if(key < 0)
{
perror("ftok");
return -1;
}
printf("key = %#x\n", key);
//通过key值获取shmid号
int shmid = shmget(key, 32, IPC_CREAT|0777);
if(shmid < 0)
{
perror("shmget");
return -1;
}
printf("shmid = %d\n", shmid);
//将共享内存映射到用户空间
void* addr = shmat(shmid, NULL, 0);
if((void*)-1 == addr)
{
perror("shmat");
return -1;
}
printf("addr = %p\n", addr);
//存储一个str
*(int *)addr = 0;
char *str = (char *)addr+4;
//存储一个字符串
strcpy(str,"1234567");
pid_t cpid = fork();
if(cpid > 0)
{
while(1)
{
if(*(int *)addr == 0)
{
printf("%s\n",(char *)addr+4);
*(int *)addr = 1;
}
}
}
else if(0 == cpid)
{
while(1)
{
if(*(int*)addr == 1)
{
char *star = (char *)addr+4;
char *end = (char*)addr+4+strlen(str)-1;
while(star<end)
{
char temp = *star;
*star = *end;
*end = temp;
star++;
end--;
}
*(int*)addr = 0;
}
}
}
else
{
perror("fork");
return -1;
}
system("ipcs -m");
return 0;
}