一、研究signal函数的第二个参数。
现实例子:
假设将来收到"请我吃饭"这个信号,我就说好,一起去绿地。 --> 默认动作
假设将来收到"请我吃饭"这个信号,我就打印helloworld。 --> 自定义动作
假设将来收到"请我吃饭"这个信号,我理都不理你。 --> 信号忽略
1、自定义动作。 (收到信号之后,想做什么事情,由你自己决定)
the address of a programmer-defined function (a "signal handler").
//填一个信号处理函数 --> 那么将来收到信号,就会自动执行该函数。
void func(int sig) //信号处理函数必须长这个样子
{
printf("helloworld");
}
signal("请我吃饭",func);
2、信号忽略。 (收到信号之后,没有任何反应)
SIG_IGN
signal("请我吃饭",SIG_IGN); //将来收到这个信号,不会有任何的动作。
3、默认动作。 (收到信号之后,就会做相应的动作)
SIG_DFL
signal("请我吃饭",SIG_DFL); -> 将来收到这个信号,就会执行这个信号的默认动作
----------------------------------------------------
void func(int sig)
{
printf("helloworld\n");
}
int main(int argc,char *argv[])
{
//signal(SIGINT,func); //自定义动作
//signal(SIGINT,SIG_IGN); //信号忽略
signal(SIGINT,SIG_DFL); //默认动作
pause();
return 0;
}
----------------------------------------------------
例题: 验证以下结论:
The signals SIGKILL and SIGSTOP cannot be caught or ignored.
//SIGKILL和SIGSTOP这两个信号不能够被捕捉或者忽略
#include
#include
#include
void func(int sig)
{
printf("helloworld!\n");
}
int main(int argc,char *argv[])
{
//1. 尝试捕捉SIGKILL
//signal(SIGKILL,func);
//2. 尝试忽略SIGKILL
signal(SIGKILL,SIG_IGN);
pause();
return 0;
}
二、信号集。
1、什么是信号集?
信号集是一个集合来的,而每一个成员都是一个信号来的。
2、为什么要学习信号集?
通过将信号加入到信号集中,再设置一个阻塞状态给信号集,那么这个信号集中所有的信号都会有阻塞状态。
3、信号阻塞与信号忽略有什么区别?
信号响应:收到信号之后,会响应信号的动作。
信号忽略:收到信号之后,直接丢弃这个信号。
信号阻塞:进程在阻塞某一个信号的前提下,收到了这个信号,不会马上响应,而是要等到解除阻塞才可以响应。
(这个信号再没有被响应的时候,不会丢弃,而是放在一个挂起队列)
三、信号集处理函数?
1、定义信号集。
信号集其实就是一个变量,数据类型是:sigset_t
定义一个信号集: sigset_t set;
2、清空信号集。 --> 将所有的信号都剔除到集合之外。 --> sigemptyset() --> man 3 sigemptyset
#include
int sigemptyset(sigset_t *set);
参数:
set: 信号集的地址
返回值:
成功:0
失败:-1
3、快速将linux下所有的信号都加入到信号集中。 --> sigfillset() -> man 3 sigfillset
#include
int sigfillset(sigset_t *set);
参数:
set: 信号集的地址
返回值:
成功:0
失败:-1
4、将某一个信号加入到信号集中。 --> sigaddset() --> man 3 sigaddset
#include
int sigaddset(sigset_t *set, int signum);
参数:
set: 信号集的地址
signum:需要添加到信号集的信号值
返回值:
成功:0
失败:-1
5、将某一个信号删除掉。 --> sigdelset() --> man 3 sigdelset
#include
int sigdelset(sigset_t *set, int signum);
参数:
set:信号集的地址
signum:需要删除的信号
返回值:
成功:0
失败:-1
6、测试某一个信号在不在集合中。 --> sigismember() --> man 3 sigismember
#include
int sigismember(const sigset_t *set, int signum);
参数:
set: 信号集的地址
signum: 需要判断的信号
返回值:
在集合中:1
不在集合中:0
出错:-1
练习1: 创建一个信号集,先将所有信号都加入到信号集中,判断一下SIGINT在不在集合中,然后清空这个信号集,判断一下SIGINT在不在集合中
再单独把SIGINT加入到信号集中,判断一下SIGINT在不在集合中,把SIGINT这个信号从信号集中删除,判断一下SIGINT在不在集合中。
#include
#include
int main(int argc,char *argv[])
{
//1. 定义信号集
sigset_t set;
//2. 将所有的信号都添加到信号集中
sigfillset(&set);
//3. 判断一下在不在集合中
int ret;
ret = sigismember(&set,SIGINT);
if(ret == 1)
printf("is member!\n"); //√
else
printf("is not member\n");
//4. 清空信号集
sigemptyset(&set);
//5. 判断一下在不在集合中
ret = sigismember(&set,SIGINT);
if(ret == 1)
printf("is member!\n");
else
printf("is not member\n");//√
//6. 单独将SIGINT信号添加到信号集中
sigaddset(&set,SIGINT);
//7. 判断一下在不在集合中
ret = sigismember(&set,SIGINT);//√
if(ret == 1)
printf("is member!\n");
else
printf("is not member\n");
//8. 将SIGINT删除
sigdelset(&set,SIGINT);
//9. 判断一下在不在集合中
ret = sigismember(&set,SIGINT);
if(ret == 1)
printf("is member!\n");
else
printf("is not member\n");//√
return 0;
}
四、如何设置信号集为阻塞状态/解除阻塞状态? --> sigprocmask() -> man 2 sigprocmask
功能: examine and change blocked signals
//设置阻塞状态
使用格式:
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:
SIG_BLOCK --> 想设置信号集set为阻塞状态
SIG_UNBLOCK --> 解除信号集set的阻塞状态
set: 信号集的地址
oldset:保留之前状态的指针,如果不关心,则设置为NULL。
返回值:
成功:0
失败:-1
例如:
sigset_t set;
把一些信号加入set中
sigprocmask(SIG_BLOCK,&set,NULL); --> 设置set信号集为阻塞状态
--> 这个信号集中的信号都是阻塞状态
--> 以后这个进程收到信号集中的信号,不会马上响应,而是把信号放在挂起队列上。
.....
..... -> 在这段时间内,信号集都是阻塞。
sigprocmask(SIG_UNBLOCK,&set,NULL); --> 解除阻塞
--> 马上去响应挂起队列上的信号
练习2:先让进程产生一个子进程。
父进程将SIGUSR1添加到信号集中,判断信号集在不在集合中,然后给信号集设置阻塞状态,让该状态持续10s(那么在10s,都是阻塞的)
10s,解除阻塞,看看会不会响应该信号。
子进程在5s时,发送信号给父进程。
子进程信号发送出去之后,马上响应 -> 信号响应
10s时才响应 -> 信号阻塞 √
永远都不响应 -> 信号忽略
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void func(int sig)
{
printf("helloworld!\n");
}
int main(int argc,char *argv[])
{
//1. 先产生一个子进程
pid_t x;
x = fork();
if(x > 0)
{
//2. 父进程先捕捉SIGUSR1
signal(SIGUSR1,func);
//3. 定义一个信号集,并将SIGUSR1添加到信号集中
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
int ret;
ret = sigismember(&set,SIGUSR1);
if(ret == 1)
printf("SIGUSR1 is member!\n");
else
printf("SIGUSR is not member!\n");
//4. 设置阻塞状态
sigprocmask(SIG_BLOCK,&set,NULL);
//5. 持续10s
int i;
for(i=10;i>0;i--)
{
sleep(1);
printf("%d\n",i);
}
//6. 解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);
//7. 主动回收子进程的资源
wait(NULL);
//8. 父进程
exit(0);
}
if(x == 0)
{
sleep(5);
kill(getppid(),SIGUSR1);
printf("I send SIGUSR1 to parent!\n");
exit(0);
}
return 0;
}
五、linux IPC对象。
1、什么是IPC对象?
在linux下,IPC对象指的是消息队列、共享内存、信号量。如果用户需要使用IPC对象来实现进程之间的通信,首先必须为IPC对象申请对应的资源。
例如:如果我们想使用消息队列来通信,那么就必须为消息队列申请key值与ID号。
2、查看系统中所有的IPC对象?
1)查看所有的IPC对象: ipcs -a
gec@ubuntu:/mnt/hgfs/GZ2180/01-课程记录/10-系统编程/03-系统编程-01.14/01-代码$ ipcs -a
------ Message Queues -------- //消息队列
key msqid owner perms used-bytes messages
key值 消息队列ID号
------ Shared Memory Segments -------- //共享内存
key shmid owner perms bytes nattch status
key值 共享内存ID号
0x00000000 425984 gec 600 524288 2 dest
0x00000000 524289 gec 600 16777216 2
0x00000000 983042 gec 600 524288 2 dest
0x00000000 786435 gec 600 524288 2 dest
0x00000000 884740 gec 600 524288 2 dest
0x00000000 1212421 gec 600 524288 2 dest
0x00000000 2424838 gec 600 524288 2 dest
0x00000000 1245191 gec 600 524288 2 dest
0x00000000 1277960 gec 600 67108864 2 dest
0x00000000 1474569 gec 600 524288 2 dest
0x00000000 1572874 gec 600 524288 2 dest
0x00000000 1605643 gec 600 4194304 2 dest
0x00000000 1703948 gec 600 524288 2 dest
0x00000000 2228237 gec 600 2097152 2 dest
0x00000000 1835022 gec 600 524288 2 dest
------ Semaphore Arrays -------- //信号量
key semid owner perms nsems
key值 信号量ID
key值: 类似于每一个人的身份证号码
ID:类似于使用身份证刷出来的排队号码
2)删除IPC对象
想删除消息队列: ipcrm -Q 消息队列的key值 / ipcrm -q 消息队列的ID号
想删除共享内存: ipcrm -M 共享内存的key值 / ipcrm -m 共享内存的ID号
想删除信号量: ipcrm -S 信号量的key值 / ipcrm -s 信号量的ID号
3、既然说使用IPC对象之前,要申请key值,那么这个key值怎么来的? --> ftok() --> man 3 ftok
功能: ftok - convert a pathname and a project identifier to a System V IPC key
//提供一个合法的路径和一个数字,就可以得到一个key值
使用格式:
#include
#include
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:一个合法的路径 "."
proj_id:非0整数 10
返回值:
成功:key值
失败:-1
The resulting value is the same for all pathnames that name the same file, when the same value of proj_id is used.
//当文件路径与proj_id一样的时候,两个函数的返回值肯定一样。
The value returned should be different when the (simultaneously existing) files or the project IDs differ.
//只要文件路径/proj_id有一个不一样,那么返回值就会不一样。
例题: 验证key值。
#include
#include
#include
int main(int argc,char *argv[])
{
key_t key1 = ftok(".",10);
printf("key1 = %d\n",key1);
key_t key2 = ftok(".",20);
printf("key2 = %d\n",key2);
key_t key3 = ftok("..",10);
printf("key3 = %d\n",key3);
key_t key4 = ftok("..",20);
printf("key4 = %d\n",key4);
key_t key5 = ftok(".",10);
printf("key5 = %d\n",key5);
return 0;
}
运行结果:
gec@ubuntu:/mnt/hgfs/GZ2180/01-课程记录/10-系统编程/03-系统编程-01.14/01-代码$ ./key
key1 = 170787629
key2 = 338559789
key3 = 170787600
key4 = 338559760
key5 = 170787629
4、尝试创建一条消息队列。 --> msgget() --> man 2 msgget
功能: msgget - get a System V message queue identifier
//获取一个消息队列的ID号
使用格式:
#include
#include
#include
int msgget(key_t key, int msgflg);
参数:
key: key值
msgflg: IPC_CREAT|0666 --> 不存在则创建,并且消息队列是可读可写
--> 存在就不会创建
返回值:
成功:消息队列的ID号
失败:-1
练习3:尝试写一个程序,在程序中创建一条消息队列出来,顺便打印key值与ID号。
程序执行后,通过ipcs -a看看,刚才创建的消息队列在不在,如果存在,则对比key值与ID号跟程序的打印结果一不一样。
最后使用命令将该消息队列删除。
#include
#include
#include
#include
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
printf("key = %#x\n",key);
//2. 申请消息队列的ID号
int msgid = msgget(key,IPC_CREAT|0666);
printf("msgid = %d\n",msgid);
return 0;
}
六、进程之间的通信方式 --- 消息队列。
1、为消息队列申请key值。
key_t key = ftok(".",10);
2、根据key值来申请消息队列的ID号
int msgid = msgget(key,IPC_CREAT|0666);
3、如何写入数据到消息队列中? --> msgsnd() --> man 2 msgsnd
#include
#include
#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
msqid: 消息队列的ID号
msgp:写入的数据的结构体的地址
struct msgbuf {
long mtype; //消息的类型,必须是一个大于0的数字
char mtext[100]; //消息的正文
};
msgsz:数据结构体中正文的大小
msgflg:0 --> 默认是阻塞属性 (有数据就读,每数据就等待数据)
返回值:
成功:0
失败:-1
4、如何读取消息队列中的数据? --> msgrcv() --> man 2 msgrcv
#include
#include
#include
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数:
msqid: 消息队列的ID号
msgp: 接收数据的缓冲区 --> 结构体变量的地址
msgsz: 数据结构体正文的大小
msgtyp: 想读取的类型
msgflg: 0 --> 普通属性
返回值:
成功:真正读取到正文数组的字节数
失败:-1
5、如何删除消息队列? --> msgctl() --> man 2 msgctl
#include
#include
#include
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid:消息队列的ID号
cmd: IPC_RMID --> 想删除消息队列
buf: 如果是删除,则设置为NULL
返回值:
成功:0
失败:-1
例题2: 要求使用消息队列,实现两个进程之间的通信。
写端:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype; //类型
char mtext[50]; //正文
};
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 申请消息队列的ID号
int msgid = msgget(key,IPC_CREAT|0666);
//3. 不断往消息队列中发送数据
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
gec.mtype = 30;
fgets(gec.mtext,sizeof(gec.mtext),stdin);
ret = msgsnd(msgid,&gec,strlen(gec.mtext),0);
if(ret == -1)
{
printf("msgsnd error!\n");
exit(-1);
}
if(strncmp(gec.mtext,"quit",4) == 0)
{
break;
}
}
return 0;
}
读端:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype; //类型
char mtext[50]; //正文
};
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 申请消息队列的ID号
int msgid = msgget(key,IPC_CREAT|0666);
//3. 不断接收消息队列上的数据
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
ret = msgrcv(msgid,&gec,sizeof(gec.mtext),30,0);
if(ret == -1)
{
printf("msgrcv error!\n");
exit(-1);
}
printf("from queue:%s",gec.mtext);
if(strncmp(gec.mtext,"quit",4) == 0)
{
break;
}
}
//4. 删除消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}