2019年8月12日星期一
一. linux信号集概念
1. 什么是信号集?
信号集是一个集合,而每一个成员都是一个信号来的,通过把信号加入到信号集中,再设置阻塞状态给信号集,那么整个信号集中的所有信号都会变成阻塞的状态。
2. 信号阻塞与信号忽略有区别?
忽略:收到信号之后,会直接丢弃。
阻塞:在阻塞的状态下收到信号,不会马上响应,而是等待解除阻塞状态之后,才会响应。
二. 信号集处理函数?
0. 信号集怎么定义?
sigset_t是信号集的数据类型,直接定义一个变量即可,例子: sigset_t set;
1. 清空信号集 -> sigemptyset() -> man 3 sigemptyset
#include <signal.h>
int sigemptyset(sigset_t *set);
set:需要清空的信号集的地址
返回值:
成功:0
失败:-1
2.将所有linux信号加入到信号集中 -> sigfillset() -> man 3 sigfillset
#include <signal.h>
int sigfillset(sigset_t *set);
set:需要填充满信号的信号集的地址
返回值:
成功:0
失败:-1
3. 添加一个信号到信号集中 -> sigaddset() -> man 3 sigaddset
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
set: 需要添加信号的信号集的地址
signum: 需要添加的信号值
返回值:
成功:0
失败:-1
4. 从信号集中删除一个信号 -> sigdelset() -> man 3 sigdelset
#include <signal.h>
int sigdelset(sigset_t *set, int signum);
set: 需要删除信号的信号集的地址
signum: 需要删除的信号值
返回值:
成功:0
失败:-1
5. 测试一个信号是不是在信号集中。
#include <signal.h>
int sigismember(const sigset_t *set, int signum);
set:需要判断的信号集的地址。
signum:需要判断的信号值。
返回值:
signum在set中,那么返回1
signum不在set中,那么返回0
出错: -1
6. 设置信号集的阻塞状态? -> sigprocmask() -> man 3 sigprocmask
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
how:
SIG_BLOCK -> 将信号集设置为阻塞状态
SIG_UNBLOCK -> 解除信号集的阻塞状态
set: 需要设置属性的信号集的地址
oset: 保留之前状态指针,如果不关心,则填NULL。
返回值:
成功:0
失败:-1
练习1:进程产生一个子进程。
父进程把SIGUSR1加入到信号集中,判断该信号是否在集合中,设置阻塞10秒,10秒之后解除阻塞。
子进程父进程设置阻塞属性之后,发送信号给父进程,看看会不会马上响应?10秒后呢?
不会马上响应,而是要等到10秒之后才会响应。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main(int argc,char *argv[])
{
pid_t x;
x = fork();
if(x > 0)
{
signal(SIGUSR1,fun);
int ret,i;
sigset_t set; //定义一个信号集
sigemptyset(&set); //清空信号
sigaddset(&set,SIGUSR1);
ret = sigismember(&set,SIGUSR1);
if(ret <= 0)
{
printf("not member!\n");
exit(-1);
}
sigprocmask(SIG_BLOCK,&set,NULL);
for(i=10;i>0;i--)
{
sleep(1);
printf("%d\n",i);
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
wait(NULL);
exit(0);
}
if(x == 0)
{
sleep(3);
kill(getppid(),SIGUSR1);
printf("I send SIGUSR1 to parent!\n");
exit(0);
}
return 0;
}
练习2:验证阻塞掩码会被子进程继承。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
void fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main(int argc,char *argv[])
{
signal(SIGUSR1,fun);
//1. 让进程设置阻塞信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
sigprocmask(SIG_BLOCK,&set,NULL);
//2. 带着这个阻塞状态去产生一个子进程
pid_t x;
int i;
x = fork();
if(x > 0)
{
printf("parent pid = %d\n",getpid());
for(i=20;i>0;i--) //在20S内,发送SIGUSR1给父进程。
{
sleep(1);
printf("parent i = %d\n",i); //挂起队列上:SIGUSR1
}
//给父进程解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL); //会响应
printf("parent unblock!\n");
wait(NULL);
exit(0);
}
if(x == 0)
{
printf("child pid = %d\n",getpid());
//究竟有没有继承过来?
for(i=35;i>0;i--) //在35S内,发送SIGUSR1给子进程。
{
sleep(1);
printf("child i = %d\n",i); //挂起队列上:SIGUSR1
}
//给子进程解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL); //会响应
printf("child unblock!\n");
exit(0);
}
return 0;
}
练习3:在进程的挂起队列中,没有相同的信号(即相同的信号会被丢弃)
父进程:
sigprocmask(SIG_BLOCK,&set,NULL);
for(i=10;i>0;i--)
{
sleep(1);
printf("%d\n",i);
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
wait(NULL);
exit(0);
子进程:
sleep(3);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
kill(getppid(),SIGUSR1);
printf("I send SIGUSR1 to parent!\n"); -> 结果: 只会响应一次SIGUSR1。
二. linux IPC对象
1. 什么是IPC对象?
在linux下,IPC对象指的是消息队列,共享内存,信号量。如果用户想在程序中使用IPC对象进行进程之间通信,首先必须申请IPC对象对应的资源。例子: 想使用消息队列,必须先申请消息队列对应的ID号(key值)。
2. 查看系统所有的IPC对象
1)查看IPC对象: ipcs -a
------ Shared Memory Segments -------- //共享内存
key shmid owner perms bytes nattch status
------ Semaphore Arrays -------- //信号量
key semid owner perms nsems
------ Message Queues -------- //消息队列
key msqid owner perms used-bytes messages
key: key值,每一个IPC对象都有一个唯一的key值。
id: ID号,是根据唯一的key值申请而来的。
2)删除IPC对象
消息队列: ipcrm -q key值 / ipcrm -q 消息队列ID
共享内存: ipcrm -m key值 / ipcrm -m 共享内存ID
信号量: ipcrm -s key值 / ipcrm -s 信号量ID
3. 在linux下,如果需要使用任何一个IPC对象,都必须先申请对应的key值。 -> ftok() -> man 3 ftok
功能: convert a pathname and a project identifier to a System V IPC key
//给定一个合法的路径以及一个整数,就可以得到一个key值。
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname:一个合法路径。 常用: "."
proj_id: 非0整数 128 常用:"10"
返回值:
成功: key值
失败: -1
注意: 当参数全部一致时,得到的key值是一样。
100 = ftok(".",10);
ftok(".",10); -> 这个函数的返回值必须是100。
举例子。
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
key_t key;
key = ftok(".",10);
printf("key = %d\n",key);
key =ftok("..",10);
printf("key = %d\n",key);
key =ftok(".",20);
printf("key = %d\n",key);
key =ftok(".",10);
printf("key = %d\n",key); -> 与第一个一样!
return 0;
}
三. 进程之间通信方式 - 消息队列
1. 管道通信与消息队列通信非常相似,有什么差异?
管道通信:不能读取特定的数据,只要管道中有数据,就一定要读取出来,操作数据时使用文件IO函数。
消息队列通信: 可以读取特定类型的数据,有数据但是数据类型不符合,那么就可以不读取,操作数据时使用消息队列特定的函数。
2. 消息队列的机制?
每一个在消息队列中数据都包含"类型+正文",读取方只需要提供类型,就可以读取到消息队列中相对应的数据了。
3. 消息队列对应的函数接口?
0)由于是使用IPC对象,所以必须要申请key值。
key = ftok(".",10);
1)根据key值申请消息队列的ID号。 -> msgget() -> man 2 msgget
功能:get a message queue identifier -> 获取消息队列的ID号
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key: 消息队列对应的key值。
msgflg: IPC_CREAT|0666 -> 不存在则创建
IPC_EXCL -> 存在则报错
The execute permissions are not used. -> 消息队列中执行权限是无效的,不被使用,0777与0666一致的!
返回值:
成功:消息队列的ID号
失败:-1
2)往消息队里中发送数据。 -> msgsnd() -> man 2 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);
msqid:消息队列的ID
msgp:写入到消息队列的结构体缓冲区的地址
struct msgbuf {
long mtype; //消息的类型,必须>0
char mtext[x]; //消息的正文,自定义的数组/结构体 x由你来决定
};
msgsz:消息正文的大小
msgflg:普通属性 -> 0
返回值:
成功:0
失败:-1
3)从消息队列中读取数据。 -> msgrcv() -> man 3 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);
msqid:消息队列的ID
msgp:读取消息队列中数据的结构体缓冲区
struct msgbuf {
long mtype; //消息的类型,必须>0
char mtext[x]; //消息的正文,自定义的数组/结构体 x由你来决定
};
msgsz:消息队列正文大小
msgtyp:读取的类型
msgflg:普通属性 -> 0
返回值:
成功:正文的字节数
失败:-1
4)删除消息队列的ID -> msgctl() -> man 2 msgctl -> 一般是读端来完成。
使用格式:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid:消息队列的ID
cmd: IPC_RMID -> 删除消息队列命令
buf: 如果是删除,则填NULL。
返回值:
成功:0
失败:-1
练习4:使用消息队列,完成两个进程之间的通信。
/* 读端 */
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值申请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),10,0);
if(ret == -1)
{
printf("msgrcv error!\n");
exit(0);
}
printf("from peer:%s",gec.mtext);
if(strncmp(gec.mtext,"quit",4) == 0)
{
break;
}
}
//4. 删除消息队列
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
/* 写端 */
#include "head.h"
struct msgbuf{
long mtype;
char mtext[50];
};
int main(int argc,char *argv[])
{
//1. 申请key值
key_t key = ftok(".",10);
//2. 根据key值申请ID号
int msgid = msgget(key,IPC_CREAT|0666);
//3. 定义数据缓冲区
struct msgbuf gec;
int ret;
while(1)
{
bzero(&gec,sizeof(gec));
gec.mtype = 10; //类型
fgets(gec.mtext,50,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;
}