目录
- 一、信号
- 1.1 概念
- 1.2 信号的响应方式⭐⭐⭐
- 1.3 几种常见的信号
- 1.4 函数
- 练习
- 二、共享内存
- 2.1 共享内存的特点
- 2.2 共享内存创建步骤⭐⭐
- 2.3 共享内存创建所需函数
- 信号主要用来通知进程异步事件的发生。最初信号设计的目的是为了处理错误,它们也用来作为最基本的IPC机制。在Linux中可以识别64种不同的信号。这些信号中的大部分都有了预先定义好的意义,但是至少有两个,SIGUSR1和SIGUSR2可以由应用程序来定义。进程可以显式地用kill 或是killg系统函数来向另一个进程或进程组发信号。此外,内核可以内应不同的事件而产生内部信号。例如,当按下终端键“Ctrl+C" 时,内核便发送一个SIGINT信号到前台的进程。
- 作为一种IPC机制,信号有一些局限性信号的系统开销太大。发送信号的进程要进行系统调用;内核要中断接收信号的进程,而且要管理它的堆栈,同时还要调用处理程序,之后还要恢复执行被中断的进程。更重要的是,信号的数量非常有限,因为只存在有限的不同的信号,而且信号能传送的信息量十分有限,用户产生的信号不可能发送附加信息及各种
- Linux是一个多用户、多任务的操作系统,无论是操作系统与一般进程间的通信,还是用户进程间的通信都是必要的。信号是进程间互相通信的方法之一,它用来指出某种事件的发生。
- 在Linux系统中,针对不同的软/硬件状况,内核程序会发送出不同的信号来通知进程某个事件的发生。但是如何处理这个信号,就要由进程本身来处理。
- 信号可以由系统内核程序发出,也能由某些进程发送,但是大部分的时候都是由内核程
序发出的。- 当一个信号正在被处理时,所有同样的信号都将暂时搁置,直到这个信号处理完成。
一、信号
1.1 概念
- 信号是在软件层次上对中断的一种模拟,是一种异步通信方式
- 信号可以直接进行用户空间进程与内核进程之间的交互,内核进程也可以利用信号通知用户进程发生了哪些系统事件。
- 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
1.2 信号的响应方式⭐⭐⭐
- 忽略信号:对信号不做任何处理。但是有两个信号不能被忽略STGKILL和SIGSTOP
- 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
- 执行缺省操作:linux对每种信号都规定了默认操作。
1.3 几种常见的信号
Linux中可以识别的信号种类有64种,在终端可以用 kill -l 列出:
需要了解的信号如下:
2)SIGINT:结束进程,对应快捷方式ctrl+c
3)SIGQUIT:退出信号,对应快捷方式ctrl+
9)SIGKILL:结束进程,不能被忽略不能被捕捉
15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
17)SIGCHLD:子进程状态改变时给父进程发的信号
19)SIGSTOP:结束进程,不能被忽略不能被捕捉
20)SIGTSTP:暂停信号,对应快捷方式ctrl+z
26)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。
1.4 函数
-
kill
int kill(pid_t pid, int sig);- 功能:信号发送
- 参数:
- pid:指定进程
- sig:要发送的信号
- 返回值:成功 0;失败 -1
-
raise
int raise(int sig);- 功能:进程向自己发送信号
- 参数:sig:信号
- 返回值:成功 0;失败 -1
-
pause
int pause(void);- 功能:用于将调用进程挂起(不占用CPU资源),直到收到信号为止。
函数测试:
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("start...\n");
/*1.kill给指定的进程发送指定的信号*/
// kill(getpid(),SIGINT);
/*2.raise给自己发送信号*/
// raise(2);//给自己发送信号
//printf("end...\n");
/*3.pause挂起进程,直到捕捉到信号才会返回*/
pause(); //不占用cpu
// while(1); //一直轮寻,占用CPU
return 0;
}
在还没有信号触发时,为了保证进程不立即结束,可以用while或者pause,但是while会一直轮询,占用cpu资源,而pause会将进程挂起,不占用CPU资源。
- signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);- 功能:信号处理函数
- 参数:
- signum:要处理的信号
- handler:信号处理方式
- SIG_IGN:忽略信号
- SIG_DFL:执行默认操作
- handler:捕捉信号(捕捉到信号要处理的函数)
- void handler(int sig){} //函数名可以自定义
- 返回值:成功:设置之前的信号处理方式;失败:-1
函数测试:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig)
{
if (sig == SIGINT)
printf("You pressed ctrl + c!\n");
else if (sig == SIGTSTP)
printf("You pressed ctrl + z!\n");
else if (sig == SIGQUIT)
printf("You pressed ctrl + \\!\n");
else
printf("end...\n");
}
int main(int argc, char const *argv[])
{
/*1.忽略信号*/
// signal(2,SIG_IGN); //ctrl+c被忽略,终端用ctrl+\退出
/*2.缺省(默认)操作*/
// signal(SIGINT,SIG_DFL);
/*3.捕捉信号*/
signal(2, handler); //捕捉结束进程信号(ctrl+C)
signal(SIGTSTP, handler); //捕捉暂停信号(ctrl+Z)
signal(SIGQUIT,handler); //捕捉推出信号(ctrl+Q)
//挂起进程,直到捕捉到信号才会返回
pause();
return 0;
}
-
alarm
unsigned int alarm(unsigned int seconds)- 功能:在进程中设置一个定时器
- 参数:seconds:定时时间,单位为秒
- 返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意📢:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
函数测试:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("start...\n");
// alarm(5); //alarm不阻塞,当时间到达后结束进程
printf("%d\n",alarm(5));//第一次调用alarm返回值为0
//alarm不阻塞,当时间到达后结束进程
printf("end...\n");
sleep(3);
printf("%d\n",alarm(8)); // 前面定时5秒。用sleep函数消耗1秒,alarm返回5-3=2
printf("second end...\n");
alarm(0); //当秒数为0时,定时被取消
pause(); //pause挂起进程(用while也可以),直到捕捉到信号才会返回
return 0;
}
练习
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let’s gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。—>父子进程,父:司机 子:售票员
/*
练习一:
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
*/
/*
分析:各个进程要捕捉和忽略的信号
售票员(子进程):
捕捉:SIGINT,SIGQUIT,SIGUSR1
忽略:SIGTSTP
司机(父进程):
捕捉:SIGTSTP,SIGUSR1,SIGUSR2
忽略:SIGINT,SIGQUIT
*/
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t pid; //pid设置为为全局变量
void saller(int arg)
{
if(arg == SIGINT)
kill(getppid(),SIGUSR1);
if(arg == SIGQUIT)
kill(getppid(),SIGUSR2);
if(arg == SIGUSR1)
{
printf("售票员:please get off the bus\n");
exit(0);
}
}
void driver(int arg)
{
if(arg == SIGUSR1)
printf("司机:let's gogogo\n");
if(arg == SIGUSR2)
printf("司机:stop the bus\n");
if(arg == SIGTSTP)
{
kill(pid,SIGUSR1);//要注意pid要为全局变量
wait(NULL);//阻塞回收子进程资源
exit(0);
}
}
int main(int argc, char const *argv[])
{
pid = fork();//创建父子进程
if(pid < 0)
{
perror("fork err");
return -1;
}
else if(pid == 0) //售票员(子进程)
{
//子进程要捕捉的信号
signal(SIGINT,saller);
signal(SIGQUIT,saller);
signal(SIGUSR1,saller);
//子进程要忽略的信号
signal(SIGTSTP,SIG_IGN);
}
else //司机(父进程)
{
//父进程要捕捉的信号
signal(SIGTSTP,driver);
signal(SIGUSR1,driver);
signal(SIGUSR2,driver);
//父进程要忽略的信号
signal(SIGINT,SIG_IGN);
signal(SIGQUIT,SIG_IGN);
}
while(1)
pause();//这样比只有一个while占用CPU更少
return 0;
}
📢要明白:最后的while(1) pause(); 的用意→如果只用一个pause(),验证一个信号后程序立即结束;如果只用while(1)进行阻塞,虽然可以起到从终端循环输入信号并打印出结果,但是cpu占用资源较高;而使用while(1)和pause()则解决以上弊端,因为这样程序运行起来后,进while,在pause处程序挂起,不再占用CPU,直到产生信号解除pause阻塞进入下一次while。
pause()作用:用于将调用进程挂起,直到收到信号为止
二、共享内存
2.1 共享内存的特点
- 共享内存是一种最为 高效 的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
- 进程可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
- 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
2.2 共享内存创建步骤⭐⭐
- 创建key值(ftok)
- 创建或打开共享内存(shmget)
- 映射共享内存到用户空间(shmat)
- 取消映射(shmdt)
- 删除共享内存(shmctl)
2.3 共享内存创建所需函数
-
ftok
key_t ftok(const char *pathname, int proj_id);- 功能:产生一个独一无二的key值
- 参数:
- pathname:已经存在的可访问文件的名字
- proj_id:一个字符(因为只用低8位)
- 返回值:成功:key值;失败:-1
运行结果:
前两位是ftok第二个字符参数的,ASCII码值的十六进制表示形式
中间这两位大多情况是01
查看文件inode👇
将文件的inode转换为十六进制👇,可以看到其后四位确实是key值十六进制的后四位
-
shmget
int shmget(key_t key, size_t size, int shmflg);- 功能:创建或打开共享内存
- 参数:
- key 键值
- size 共享内存的大小
- shmflg IPC_CREAT|IPC_EXCL(判错)|0666
- 返回值:成功 shmid;出错 -1
例:
查看系统共享内存:ipcs -m
删除共享内存:ipcrm -m 对应的shmid
-
shmat
void *shmat(int shmid,const void *shmaddr,int shmflg);- 功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
- 参数:
- shmid 共享内存的id号
- shmaddr 一般为NULL,表示由系统自动完成映射;如果不为NULL,那么由用户指定
- shmflg:
- SHM_RDONLY就是对该共享内存只进行读操作
- 0 可读可写
- 返回值:
- 成功:完成映射后的地址,
- 失败:-1的地址
用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)
常用下面:
char *p = shmat(shmid,NULL,0);
if(p==(void *)-1)
{
perror("shmat err");
return -1;
}
-
shmdt
int shmdt(const void *shmaddr);- 功能:取消映射
- 参数:要取消的地址
- 返回值:成功:0 ;失败:-1
-
shmctl
int shmctl(int shmid,int cmd,struct shmid_ds *buf);- 功能:(删除共享内存),对共享内存进行各种操作
- 参数:
- shmid :共享内存的id号
- cmd
- IPC_STAT 获得shmid属性信息,存放在第三参数
- IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
- IPC_RMID:删除共享内存,此时第三个参数为NULL即可
- 返回:成功0 ;失败-1
用法:shmctl(shmid,IPC_RMID,NULL);
例:创建共享内存,通过共享内存完成读写
/*
共享内存创建及读写
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
/*1.创建key值*/
key_t key;
key = ftok("./6kill.c", 'a'); //第二个参数是任意字符
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("%#x\n", key); //'#'会在打印结果中添加前缀0x
/*2.创建或打开共享内存*/
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid <= 0) //=0不使用
{
//若已创建,则用shmget打开,重新给shmid赋值
if (errno == 17)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
printf("shmid:%d\n", shmid);
/*3.映射共享内存到用户空间 */
char *p = shmat(shmid, NULL, 0);
if (p == (void *)-1)
{
perror("shmat err");
return -1;
}
//读写操作
read(0, p, 32);
write(1, p, 32);
//printf("buf:%s\n",p);
/*4.撤销映射*/
shmdt(p);
/*5.删除共享内存*/
shmctl(shmid, IPC_RMID, NULL);
return 0;
}