您的位置:首页 > 运维架构 > Linux

alin的学习之路(Linux系统编程:八)(匿名映射、信号)

2020-07-22 20:29 330 查看

alin的学习之路(Linux系统编程:八)(匿名映射、信号)

1. 匿名映射

父子进程之间的通信如果用普通的共享存储映射会浪费很多的资源,而这些资源是没有必要去用到的,所以有了匿名映射

匿名映射无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。

使用MAP_ANONYMOUS

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
  • 4"随意举例,该位置表示映射区大小,可依实际需要填写。
  • MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。

示例代码:

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

#define SIZE 128

int main()
{
pid_t pid;
char buf[SIZE];
void *addr = NULL;

addr = mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
if(MAP_FAILED == addr)
{
perror("mmap");
return 1;
}

pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{
memcpy(addr,"hello itcast",strlen("hello itcast")+1);
munmap(addr,1024);
exit(0);
}
else
{
wait(NULL);
memset(buf,0,SIZE);
memcpy(buf,addr,SIZE);
printf("buf = %s\n",buf);
printf("addr = %s\n",(char*)addr);
munmap(addr,1024);
}

return 0;
}

2. 信号

1.概述

信号可以使一个正在运行的进程发生中断,处理完该信号后再执行之前的进程。

信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

信号的特点

  • 简单
  • 不能携带大量信息
  • 满足某个特设条件才发送

一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。

注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

2. 信号的编号与名称

kill -l
可查看所有信号的编号

itcast@ubuntu:~/classcode/8day$ kill -l
1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
编号 信号 对应事件 默认动作
1 SIGHUP 用户退出shell时,由该shell启动的所有进程将收到这个信号 终止进程
2 SIGINT 当用户按下了**<Ctrl+C>**组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号 终止进程
3 SIGQUIT 用户按下**<ctrl+>**组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号 终止进程
4 SIGILL CPU检测到某进程执行了非法指令 终止进程并产生core文件
5 SIGTRAP 该信号由断点指令或其他 trap指令产生 终止进程并产生core文件
6 SIGABRT 调用abort函数时产生该信号 终止进程并产生core文件
7 SIGBUS 非法访问内存地址,包括内存对齐出错 终止进程并产生core文件
8 SIGFPE 在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误 终止进程并产生core文件
9 SIGKILL 无条件终止进程。本信号不能被忽略,处理和阻塞 终止进程,可以杀死任何进程
10 SIGUSE1 用户定义的信号。即程序员可以在程序中定义并使用该信号 终止进程
11 SIGSEGV 指示进程进行了无效内存访问(段错误) 终止进程并产生core文件
12 SIGUSR2 另外一个用户自定义信号,程序员可以在程序中定义并使用该信号 终止进程
13 SIGPIPE Broken pipe向一个没有读端的管道写数据 终止进程
14 SIGALRM 定时器超时,超时的时间 由系统调用alarm设置 终止进程
15 SIGTERM 程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号 终止进程
16 SIGSTKFLT Linux早期版本出现的信号,现仍保留向后兼容 终止进程
17 SIGCHLD 子进程结束时,父进程会收到这个信号 忽略这个信号
18 SIGCONT 如果进程已停止,则使其继续运行 继续/忽略
19 SIGSTOP 停止进程的执行。信号不能被忽略,处理和阻塞 为终止进程
20 SIGTSTP 停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号 暂停进程
21 SIGTTIN 后台进程读终端控制台 暂停进程
22 SIGTTOU 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生 暂停进程
23 SIGURG 套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达 忽略该信号
24 SIGXCPU 进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程 终止进程
25 SIGXFSZ 超过文件的最大长度设置 终止进程
26 SIGVTALRM 虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间 终止进程
27 SGIPROF 类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间 终止进程
28 SIGWINCH 窗口变化大小时发出 忽略该信号
29 SIGIO 此信号向进程指示发出了一个异步IO事件 忽略该信号
30 SIGPWR 关机 终止进程
31 SIGSYS 无效的系统调用 终止进程并产生core文件
34~64 SIGRTMIN ~ SIGRTMAX LINUX的实时信号,它们没有固定的含义(可以由用户自定义) 终止进程

3. 信号的四要素

每个信号必备4要素,分别是:

1)编号 2)名称 3)事件 4)默认处理动作

可以通过 man 7 signal 获取

Action为默认动作:

  • Term:终止进程
  • Ign: 忽略信号 (默认即时对该种信号忽略操作)
  • Core:终止进程,生成Core文件。(查验死亡原因,用于gdb调试)
  • Stop:停止(暂停)进程
  • Cont:继续运行进程

这里特别强调了9) SIGKILL 和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将其设置为阻塞。

4. 信号的状态

  1. 产生

a) 当用户按某些终端键时,将产生信号。

终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT

终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT

终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。

b) 硬件异常将产生信号。

除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

c) 软件异常将产生信号。

当检测到某种软件条件已发生(如:定时器alarm),并将其通知有关进程时,产生信号。

d) 调用系统函数(如:kill、raise、abort)将发送信号。

注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

e) 运行 kill /killall命令将发送信号。

此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

  1. 未决

    已经产生的信号未被处理,是未决状态

  2. 递达

    已经产生的信号被处理了,即为递达状态

5. 阻塞信号集和未决信号集

Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集

信号集

sigset_t set
,信号集的底层是位图

  1. 阻塞信号集

将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。

  1. 未决信号集

信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。

信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。

信号不支持排队,所以如果有多个相同信号发送过来,只会响应一次

6.信号产生函数

  1. kill 函数

    #include <sys/types.h>
    #include <signal.h>
    int kill(pid_t pid, int sig);

super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。

kill -9 (root用户的pid) 是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。

普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;

pid = fork();
if(-1 == pid)
{
perror("fork");
return 1;
}
else if(0 == pid)
{

for(int i=0 ;i<5 ;++i)
{
printf("子进程 do work %d\n",i);
sleep(1);
}
exit(0);
}
else
{
printf("父进程三秒后杀掉子进程\n");
sleep(3);

kill(pid,SIGINT);
printf("子进程被杀死\n");
}
return 0;
}
  1. raise 函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
for(int i=0 ;i<5 ;++i)
{
printf("do work %d\n",i);
sleep(1);
}
raise(SIGINT);
printf("hello itcast\n");

return 0;
}
  1. abort 函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
for(int i=0 ;i<5 ;++i)
{
printf("do work %d\n",i);
sleep(1);
}
abort();   //给自己发送SIGABRT
printf("hello itcast\n");

return 0;
}
  1. alarm 函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main()
{
int ret = alarm(3);
printf("ret = %d\n",ret);
for(int i=0 ;i<5; ++i)
{
printf("do work %d\n",i);
sleep(1);
}
return 0;
}
  1. settimer函数
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
void func(int signo)
{
printf("捕捉了信号%d\n",signo);
}
int main()
{
int i = 0;
struct itimerval tmv = {
//设定后第一次延时的秒数
.it_interval = {
.tv_sec = 1,
.tv_usec = 0
},
//设定后每几秒执行一次function
.it_value = {
.tv_sec = 1,
.tv_usec = 0
}
};

if(SIG_ERR ==signal(SIGALRM,func))
{
perror("signal");
return 1;
}
int ret = setitimer(ITIMER_REAL,&tmv,NULL);
if(-1 == ret)
{
perror("setitimer");
return 1;
}

while(1)
{
printf("do work %d\n",i);
++i;

sleep(1);
}
return 0;
}

7. 信号集

在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。

  1. 自定义信号集
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

int main()
{
sigset_t set;

//将集合置空
sigemptyset(&set);
printf("信号为1:");
for(int i=1; i<=31 ;++i)
{
if(sigismember(&set,i))
printf("%d ",i);
}
printf("\n");

//将集合全部信号置1
sigfillset(&set);
printf("信号为1:");
for(int i=1; i<=31 ;++i)
{
if(sigismember(&set,i))
printf("%d ",i);
}
printf("\n");

printf("=============================\n");

sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
printf("信号为1:");
for(int i=1; i<=31 ;++i)
{
if(sigismember(&set,i))
printf("%d ",i);
}
printf("\n");

printf("=============================\n");

sigdelset(&set,SIGQUIT);
printf("信号为1:");
for(int i=1; i<=31 ;++i)
{
if(sigismember(&set,i))
printf("%d ",i);
}
printf("\n");

return 0;
}
  1. sigprocmask函数

用于修改阻塞集合

信号阻塞集也称信号屏蔽集、信号掩码。每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它,直到进程准备好时再将信号通知进程)。

所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除,且对应的信号在被阻塞时发生了,进程将会收到相应的信号。

我们可以通过 sigprocmask() 修改当前的信号掩码来改变信号的阻塞情况。

#include <stdio.h>
#include <signal.h>

void func(int signo,siginfo_t *info,void* context)
{
printf("捕捉了信号%d\n",signo);
}
void fun(int signo)
{
printf("捕捉了信号%d\n",signo);
}

int main()
{
sigset_t set;
sigemptyset(&set);
//设置2、3信号阻塞
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);

struct sigaction act = {
.sa_flags = SA_SIGINFO,
.sa_sigaction = func
};
sigaction(SIGINT,&act,NULL);

if(SIG_ERR == signal(SIGQUIT,fun))
{
perror("signal");
return 1;
}
printf("按回车将信号2、3添加到阻塞集中\n");
getchar();
//添加到阻塞集中
int ret = sigprocmask(SIG_BLOCK,&set,&set);
if(-1 == ret)
{
perror("sigprocmask");
return 1;
}

printf("按回车将信号2、3解除屏蔽\n");
getchar();
//将阻塞集设置成原来的阻塞集
ret = sigprocmask(SIG_SETMASK,&set,NULL);
if(-1 == ret)
{
perror("sigprocmask");
return 1;
}

getchar();
return 0;
}
  1. sigpending函数
#include <stdio.h>
#include <signal.h>

void func(int signo,siginfo_t *info,void* context)
{
printf("捕捉了信号%d\n",signo);
}
void fun(int signo)
{
printf("捕捉了信号%d\n",signo);
}

int main()
{
sigset_t set;
sigemptyset(&set);
//设置2、3信号阻塞
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);

struct sigaction act = {
.sa_flags = SA_SIGINFO,
.sa_sigaction = func
};
sigaction(SIGINT,&act,NULL);

if(SIG_ERR == signal(SIGQUIT,fun))
{
perror("signal");
return 1;
}
printf("按回车将信号2、3添加到阻塞集中\n");
getchar();
//添加到阻塞集中
int ret = sigprocmask(SIG_BLOCK,&set,&set);
if(-1 == ret)
{
perror("sigprocmask");
return 1;
}

printf("按回车检测未决信号集\n");
getchar();

//接收未决信号集
sigset_t newset;
ret = sigpending(&newset);
if(-1 == ret)
{
perror("sigpending");
return 1;
}
//检测未决信号集中的信号
printf("未决信号为:");
for(int i=1 ;i<=31 ;++i)
{
if(sigismember(&newset,i))
printf("%d ",i);
}
printf("\n");

printf("按回车将信号2、3解除屏蔽\n");
getchar();
//将阻塞集设置成原来的阻塞集
ret = sigprocmask(SIG_SETMASK,&set,NULL);
if(-1 == ret)
{
perror("sigprocmask");
return 1;
}

getchar();

return 0;
}

8. 信号捕捉

一个进程收到一个信号的时候,可以用如下方法进行处理:

1)执行系统默认动作

对大多数信号来说,系统默认动作是用来终止该进程。

2)忽略此信号(丢弃)

接收到此信号后没有任何动作。

3)执行自定义信号处理函数(捕获)

用用户定义的信号处理函数处理该信号。

【注意】:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

  1. signal函数
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void fun(int signo)
{
printf("捕捉到信号:%d\n",signo);

}
int main()
{
int i = 0;
signal(SIGINT,fun);

if(SIG_ERR == signal(SIGINT,SIG_DFL))
perror("signal");
while(1)
{
printf("do work %d\n",i);
sleep(1);
++i;
}

return 0;
}
  1. sigaction 函数
  1. sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a) SIG_IGN:忽略该信号

b) SIG_DFL:执行系统默认动作

c) 处理函数名:自定义信号处理函数

  1. sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。

  2. sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:

Ø SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

Ø SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

Ø SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

Ø SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

Ø SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

Ø SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void func(int signo)
{
printf("捕捉了信号:%d\n",signo);
}

int main()
{
int i = 0;
struct sigaction act = {
.sa_handler = func
};

int ret = sigaction(SIGINT,&act,NULL);
if(-1 == ret)
{
perror("sigaction");
return 1;
}
while(1)
{

printf("do work %d\n",i);
++i;

sleep(1);
}

return 0;
}

sigaction总结:

  • 如果要用handler的函数指针,则在结构体中只设置sa_handler的函数指针即可
  • 如果要使用sa_sigaction的函数指针,则在结构体中还需设置
    flags = SA_SIGINFO
  1. sigqueue 函数

向指定进程发送指定信号的同时,携带数据。但如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。

示例代码:一个进程在发送信号,一个进程在接收信号的发送。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

//给制定的进程发送信号
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("./a/out pid signo");
return 1;
}
pid_t pid = atoi(argv[1]);
int signo = atoi(argv[2]);

union sigval val = {
.sival_int = 88
};

int ret = sigqueue(pid,signo,val);
if(-1 == ret)
{
perror("sigqueue");
return 1;
}
return 0;}
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void func(int signo,siginfo_t *info,void* context)
{
printf("捕捉了信号:%d\n",signo);
printf("信息为:%d,%d,%d\n",info->si_signo,info->si_int,info->si_value.sival_int);
}

int main()
{
int i = 0;
struct sigaction act = {
.sa_flags = SA_SIGINFO,
.sa_sigaction = func
};

int ret = sigaction(SIGINT,&act,NULL);
if(-1 == ret)
{
perror("sigaction");
return 1;
}
while(1)
{
printf("do work %d\n",i);
++i;
sleep(1);
}
return 0;
}

union sigval val = {
.sival_int = 88
};

int ret = sigqueue(pid,signo,val);
if(-1 == ret)
{
perror("sigqueue");
return 1;
}
return 0;

}

```c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void func(int signo,siginfo_t *info,void* context)
{
printf("捕捉了信号:%d\n",signo);
printf("信息为:%d,%d,%d\n",info->si_signo,info->si_int,info->si_value.sival_int);
}

int main()
{
int i = 0;
struct sigaction act = {
.sa_flags = SA_SIGINFO,
.sa_sigaction = func
};

int ret = sigaction(SIGINT,&act,NULL);
if(-1 == ret)
{
perror("sigaction");
return 1;
}
while(1)
{
printf("do work %d\n",i);
++i;
sleep(1);
}
return 0;
}

信号中传递的信息主要是info中的 si_signo,si_int,si_value.sival_int,里面的信息是int的数据。详细数据见manpage

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐