3.5.1.什么是信号
本节介绍什么是信号,以及信号由谁发送、由谁处理,怎么处理的问题,目的是站在一定高度上认识信号的作用和意义。
3.5.2.常见信号介绍
本节对常见需要注意的几个信号进行介绍,其他不常用信号可以暂时不理,遇到时再去查文档处理。
3.5.3.进程对信号的处理
本节介绍进程对信号的三种处理方法,signal函数和sigaction函数用来捕获信号时的差异。
3.5.4.alarm和pause函数
本节对alarm 闹钟 函数和pause函数进行讲解和实战编程,这两个函数的实现都和信号有关。
3.5.1.什么是信号
3.5.1.1、信号是内容受限的一种异步通信机制
(1)信号的目的:用来通信 (当前进程的通讯途径)
(2)信号是异步的(对比硬件中断): 信号 类似于软件中断
(3)信号本质上是int型数字编号(事先定义好的)
3.5.1.2、信号由谁发出
(1)用户在终端按下按键
(2)硬件异常后由操作系统内核发出信号
(3)用户使用kill命令向其他进程发出信号
(4)某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write时会产生SIGPIPE信号
3.5.1.3、信号由谁处理、如何处理
(1)忽略信号
(2)捕获信号(信号绑定了一个函数)
(3)默认处理(当前进程没有明显的管这个信号,默认:忽略或终止进程)
3.5.2.常见信号介绍
所有的信号 都是 事先定义好的 ,系统事先定义好的
(1)SIGINT 2 Ctrl+C时OS送给前台进程组中每个进程。 读 谁给印它。 有点像软件中断
(2)SIGABRT 6 调用abort函数,进程异常终止。 读 谁给包它
(3)SIGPOLL 和 SIGIO 8 指示一个异步IO事件,在高级IO中提及
SIGIO : https://blog.csdn.net/liangzuzong/article/details/131462970
(4)SIGKILL 9 杀死进程的终极办法 例如我们之前 kill -9
(5)SIGSEGV 11 无效存储访问时OS发出该信号 段错误就是在这个信号产生的
(6)SIGPIPE 13 异步通讯 涉及管道和socket
(7)SIGALRM 14 涉及alarm函数的实现
SIGALRM :https://blog.csdn.net/liangzuzong/article/details/131424767
(8)SIGTERM 15 kill命令发送的OS默认终止信号
(9)SIGCHLD 17 子进程终止或停止时OS向其父进程发此信号 读 谁给才有它
(10) 这两个 信号 ,是用户 自定义 信号
SIGUSR1 10 用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12
3.5.3.进程对信号的处理
3.5.3.1、signal函数介绍
man 2 signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
传参:
int signum : 信号编码
sighandler_t handler : typedef void (*sighandler_t)(int);函数指针类型
typedef void (*sighandler_t)(int);函数指针: 接受一个 int 类型的 参数, 返回一个 void 类型
这个函数指针的作用: 这个函数的作用就是向我们的操作系统内核去注册,我们当前进程对某一个信号的处理方法, 他处理方法一般就是一个函数嘛,用一个函数来处理它对不对
3.5.3.2、用signal函数处理SIGINT信号
(1)默认处理
(2)忽略处理
(3)捕获处理
(1)默认处理
1.1 默认处理程序 1
#include "stdio.h"
#include <signal.h>
int main(void)
{
printf("before while(1)\n");
while(1);
printf("接受到 SIGINT 信号 :也就是 Ctrl+C \n");
return 0;
}
/* 默认情况就是 结束这个进程 */
运行结果
1.2 默认处理程序 2
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
传参:
int signum : 信号编码
sighandler_t handler : typedef void (*sighandler_t)(int);函数指针类型
typedef void (*sighandler_t)(int);函数指针: 接受一个 int 类型的 参数, 返回一个 void 类型
这个函数指针的作用: 这个函数的作用就是向我们的操作系统内核去注册,我们当前进程对某一个信号的处理方法, 他处理方法一般就是一个函数嘛,用一个函数来处理它对不对
SIG_DFL 这个是在哪里定义的
定义路径: vi /usr/include/i386-linux-gnu/bits/signum.h
/* Fake signal functions. */
#define SIG_ERR ((__sighandler_t) -1) /* Error return. 错误吗 */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. 默认的 */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. 忽略的 */
SIG_ERR 的实质 就是 -1, 它被强制转换成 __sighandler_t 类型,
这个 __sighandler_t 其实就是 sighandler_t 类型, __sighandler_t 这里加了 两个__下划线代表示内核层的!!!
从这里大家就可以 看出来 ,其实函数指针 不一定是一个函数的地址,也可是 一个数字,只要你把这个数字的类型 强制转换成 函数指针类型 就行,
变量类型 说白了 就是 哄 编译器的, 变量类型你说了算!!
上边是介绍,下面才是 实际的代码
#include "stdio.h"
#include <signal.h>
int main(void)
{
signal(SIGINT, SIG_DFL); /* SIG_DFL: 指定SIGINT信号 默认处理 */
printf("before while(1)\n");
while(1);
printf("接受到 SIGINT 信号 :也就是 Ctrl+C \n");
return 0;
}
运行结果:
(2)忽略处理
2.1 忽略处理 SIGINT 信号 代码
#include "stdio.h"
#include <signal.h>
int main(void)
{
signal(SIGINT, SIG_IGN); /* 指定SIGINT信号 忽略处理。 SIG_IGN : 忽略处理 */
printf("before while(1)\n");
while(1);
printf("接受到 SIGINT 信号 :也就是 Ctrl+C \n");
return 0;
}
运行结果
2.2 指定SIGKIL信号 忽略处理
#include "stdio.h"
#include <signal.h>
#include <stdlib.h>
typedef void (*sighandler_t)(int); /* 1. 先 定义一个 函数指针的 类型 */
int main(void)
{
sighandler_t ret = ((sighandler_t)-2); /* 2. 再把-2 强制转换为 函数指针 类型的sighandler_t */
//ret = signal(SIGINT, SIG_IGN); /* 指定SIGINT信号 忽略处理。 SIG_IGN : 忽略处理 */
ret = signal(SIGKILL, SIG_IGN); /* SIGKILL 9 杀死进程的终极办法 例如我们之前 kill -9 指定SIGKIL信号 忽略处理。 */
if(SIG_ERR == ret) /* 因为 SIGKILL 信号不能做忽略处理, 此时 signal 的返回值就等于 SIG_ERR, */
{
perror("SIG_ERR == ret signal:");
exit(-1); /* exit(-1):这个括号不能没有,如果没括号的话程序无法退出 */
}
printf("before while(1)\n");
while(1);
printf("接受到 SIGINT 信号 :也就是 Ctrl+C \n");
return 0;
}
运行结果
(3)捕获处理
3.1 捕获处理程序1
#include "stdio.h"
#include <signal.h>
void func(int sig)
{
if (SIGINT != sig)
{
return;
}
printf("func for signal : %d \n", sig); /**/
}
int main(void)
{
signal(SIGINT, func ); /*主动捕获信号 SIGINT 信号 1.当收到 SIGINT 信号后,回去执行 func 这个函数*/
printf("before while(1)\n");
while(1);
printf("接受到 SIGINT 信号 :也就是 Ctrl+C \n");
return 0;
}
运行结果:
细节:
(1)signal函数绑定一个捕获函数后信号发生后会自动执行绑定的捕获函数,并且把信号编号作为传参传给捕获函数
(2)signal的返回值在出错时为SIG_ERR,绑定成功时返回 旧的 捕获函数
(3)
3.5.3.3、signal函数的优点和缺点
(1)优点:简单好用,捕获信号常用
(2)缺点:无法简单直接得知之前设置的对信号的处理方法
signal()的行为在不同的UNIX版本中有所不同
在不同版本的Linux上进行了历史性的修改。避免使用:
请改用sigaction(2)。请参阅下面的便携性。
3.5.3.4、sigaction函数介绍
(1)2个都是API,但是sigaction比signal更具有可移植性
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
3个传参:
1. int signum: 信号编码
2. const struct sigaction *act: 结构体指针, 加 const 是传参, 设置新的捕获函数
struct sigaction {
void (*sa_handler)(int); /* 主要用这个 */
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
3. struct sigaction *oldact: 结构体指针, 函数输出型 参数 , 获取旧的捕获函数
(2)用法关键是2个sigaction指针
结合3.5.4.alarm 闹钟
#include "stdio.h"
#include <signal.h>
#include <unistd.h>
void func(int sig)
{
if(sig == SIGALRM )
{
printf("alarm happened \n"); /* 闹钟触发 */
}
}
int main(void)
{
unsigned int ret = -1;
struct sigaction act = {0}; /* 先定义 sigaction 第二参数 act,全部 赋值为 0 */
act.sa_handler = func; /* act.sa_handler赋值,绑定 func */
//signal(SIGALRM, func); /*主动捕获信号SIGALRM 信号 1.当收到SIGALRM 信号后, 回去执行 func 这个函数*/
sigaction(SIGALRM, &act, NULL);/*主动捕获信号SIGALRM 信号 1.当收到SIGALRM 信号后, 回去执行 func 这个函数, 第3参数 NULL*/
ret = alarm(3); /* alarm 定义 3 秒钟后, 会收到 SIGALRM 一个信号 */
printf("ret = %d \n", ret); /* 第一次 调用 alarm 返回值 返回一个 0 */
while(1);
return 0;
}
运行结果:
sigaction比signal好的一点:sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。
指针函数
//打印第m个学生的成绩 指针函数的应用
#include <stdio.h>
float *find(float(*pionter)[4],int n);//函数声明
int main(void)
{
static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};//3个学生的成绩
float *p; //定义float类型的指针
int i,m;
printf("Enter the number to be found:");
scanf("%d",&m);
printf("the score of NO.%d are:\n",m);
p=find(score,m-1); //将find函数返回的地址给p
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i)); //打印(score+n)行i列数据
return 0;
}
float *find(float(*pionter)[4],int n)/*定义指针函数*/
{
float *pt; //定义float类型的指针
pt=*(pionter+n); //将(pionter+n)行的首地址给pt
return(pt); //返回float类型的地址
}
/*
共有三个学生的成绩,函数find()被定义为指针函数,其形参pointer是指针指向包含4个元素的一维数组的指针变量(即简称数组指针)。
补充:为什么*(point+n)是表示地址?而不是取值。
*(point+1)单独使用时表示的是第 1行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址。
因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;
就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
main()函数中调用find()函数,将score数组的首地址传给pointer。
1.指针函数
(1)本质是一个函数,不过它的返回值是一个指针。其声明的形式:类型名 *函数名(函数参数列表);
int * pfun(int, int);
由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数;
接着再和前面的“*”结合,说明这个函数的返回值是一个指针。由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。
(2)返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。
(3)void型指针
void指针是一种不明确类型的指针,任何指针都可转换为void指针。
指针有两个非常重要的信息:
1.指针的值(指针目标对象的内存首地址)
2.指针指向对象的类型
注意点:void指针只保存了 指针的值 并没有记录 指针指向对象的类型。因此在用到对void指针解引时,需要先把void指针转换成原本的数据类型。
int n = 500; //定义一个int变量
int * p = &n; //定义int类型指针
void * pv = p; //定义void指针,只保存了p的值(即n的内存首地址)
//错误的写法
printf("%d\n", *pv); //这里会报错,因pv指针没有明确数据类型,因此也不知道需要取多少字节的数据
//正确写法
printf("%d\n", *( (int*)pv ) ); //先把pv指针转为int类型指针,再对其解引
*/
函数指针:
2.函数指针
(1)函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。
(2)函数指针的声明方法为:
返回值类型 ( * 指针变量名) ([形参列表]);
注1:“返回值类型”说明函数的返回类型,“(指针变量名 )”中的括号不能省,括号改变了运算符的优先级。若省略整体则成为一个函数说明,说明了一个返回的数据类型是指针的函数,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如:
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针,是一个指向返回值为int的函数的指针 */
f=func; /* 将func函数的首地址赋给指针f */
或者使用下面的方法将函数地址赋给函数指针:
f = &func;
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
(3)举例:
#include<stdio.h>
int max(int x,int y){return (x>y? x:y);} //返回两个数的最大值
int main()
{
int (*ptr)(int, int); //定义指向函数的指针变量
int a, b, c;
ptr = max; //将max函数的地址给ptr
scanf("%d%d", &a, &b);
c = (*ptr)(a,b); //ptr和max函数地址相同,相当于调用max函数
printf("a=%d, b=%d, max=%d", a, b, c);
return 0;
}
ptr是指向函数的指针变量,所以可把函数max赋给ptr,作为ptr的值,即把max函数的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址。不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数。不过注意,指向函数的指针变量没有++和--运算,用时要小心。
(4)补充
函数指针可作为参数
函数指针可作为返回值
#include <stdio.h>
int add(int num1,int num2)
{
return num1+num2;
}
int sub(int num1,int num2)
{
return num1-num2;
}
int fun(int (*fp)(int,int),int num1,int num2) //函数指针做参数
{
return (*fp)(num1,num2);
}
int (*select(char c))(int,int) //函数指针作为返回值
{
switch(c)
{
case '+': return add;
case '-': return sub;
}
}
int main()
{
int num1,op,num2;
int (*fp)(int,int);
printf("请输入一个表达式,比如(1+3):\n");
scanf("%d%c%d",&num1,&op,&num2);
fp=select(op); //返回的函数指针赋予fp
printf("%d%c%d=%d\n",num1,op,num2,fun(fp,num1,num2));
/*
printf("3+5=%d\n",fun(add,3,5)); 函数名就是函数的首地址
printf("3-5=%d\n",fun(sub,3,5));
*/
return 0;
}
int (*add)(int, int);定义了一个函数指针add,用于指向返回值为int,并且有两个int参数的函数
int (*select(char c))(int,int) 分解如下:
select与后面的(char c)结合,说明是一个函数,即select(char c)
在和*结合,说明select函数的返回值是一个指针,即*(select(char c))
在和后面的(int,int)结合,说明select函数返回的指针指向函数,不是指向int类型,即int ((*select(char c)))(int,int)
总结:返回值为select函数指针,该返回值指向一个 返回值为int,两个参数为int类型的函数