赞
踩
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xv72jW1m-1673427658676)(…/牛客项目/temp/image-20230109101013452.png)]
定义——信号处理函数,注册当前信号的行为
当signum信号到来了,执行handler行为,最终返回这个信号之前的行为
#include <signal.h>
typedef void (*sighandler_t)(int);//这个int就是信号的标识
sighandler_t signal(int signum, sighandler_t handler);
标准形式:因为C中可能有重名的风险
void (*singal(int signum, void (*func)(int)))(int);
示例1
int main(int argc, char **argv) {
int i;
//SIGINT表示终端中断,shell中的ctrl+c
//SIG_IGN表示忽略信号
signal(SIGINT, SIG_IGN);
for (i = 0; i < 10; i++) {
write(1, "*", 1);
sleep(1);
}
exit(0);
}
输出结果:可以看到即使ctrl+c了依旧在接着输出,说明终止信号被忽略了,还有一点signal是给程序规定了接收到信号后的动作,在规定之后程序运行过程中只要接受到有关信号就会做出响应
**^C********
示例2
static void int_handler(int s) { write(1, "!", 1); } int main(int argc, char **argv) { int i; signal(SIGINT, int_handler); for (i = 0; i < 10; i++) { write(1, "*", 1); sleep(1); } exit(0); }
输出结果:可以看到在接受到信号的时候执行了int_handler
***^C!***^C!***^C!*
注意,当我们非常快的按ctrl+c时,可以看到程序非常快就就结束了,这是因为信号会打断阻塞的系统调用,对于这个现象,比如open(), read(), write()系统调用都有可能被信号打断,当其被信号打断时会返回EINTR(<0的数),所以需要对其返回值做判断,是EINTR时需要继续执行我们的程序。
信号的不可靠
信号处理函数的执行现场是内核布置的,当多个信号连续到来时就可能出现一个问题,后面信号的现场给前面的信号处理现场覆盖掉了,这就是信号的不可靠,要解决这种问题就引入了可重入函数
可重入函数
重入的概念:第一次调用还没结束就开始第二次调用,可能导致第一次调用出现一次不可预料的结果
所有的系统调用都是可重入的,一部分标准库函数也是可重入的,比如一些函数会有_r的版本,其中没有_r版本的就不能够用在信号处理函数当中,防止重入的现象,localtime返回的tm*所指向的内存是静态区,如果出现重入现象就会导致原先的tm结果被覆盖,所以引入_r版本,由调用的用户来决定执行结果的存放区域即result
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);
又比如memcpy函数,是可重入的
具体过程
首先的明白一个概念,就是调度,虽然宏观上看着好像我们某个程序一致占用着CPU在进行,但实际上在微观上程序实际上是交替着执行,只不过因为交替的非常快,导致我们看到的效果好像一个程序一致占用着CPU在执行,这个交替的策略,就是所谓的调度策略,现代CPU的调度策略通常都是时间片调度,就是说规定一个CPU时间片,某个进程CPU调度后只能执行这样的一个时间片的时间,执行完后就会响应时钟中断,讲这个进程切换到内核态后进入队列中等待下一次被CPU调度。理解了这个概念后,再来看信号响应的过程,以4.2中的函数为例:
内核为每个进程维护了两个和信号有关的位图,分别是:
这两个位图中的每一位就对应了一种信号,当进程接受到一个信号的时候就会将信号对应的pending位置1,这时进程可能正在被调度或者已经在调度队列里面等着了,这里需要注意在从user态到kernel态时(该过程就是时间片用完,响应CPU调度了)进程会在自己内核中的PCB(进程控制块)中保存好自己的执行现场,比如就有执行的下一条指令的地址address。
当进程再次被CPU调度的时候,也就是要从kernel态切换到user态执行时,会将mask和pending做一个与运算,这时pending位被置1的位就会被保留,这时候就知道进程接受到哪些信号了,这时,内核会将PCB中的address换成信号处理函数的入口地址后执行信号处理函数,同时将该信号在mask和pending位图中对应的位置置0,在执行完信号处理函数(打印!)以后又会回到kernel态将address换成原先main执行到的指令地址,并将mask中对应的位置1,之后再被调度时又会经历kernel态到user态,又会做mask&pending重复上面的过程。
需要注意的点
kill(2) 给pid进程发送sig信号
/* Send signal SIG to process number PID. If PID is zero,
send SIG to all processes in the current process's process group.
If PID is < -1, send SIG to all processes in process group - PID. */
extern int kill (__pid_t __pid, int __sig) __THROW;
raise(3) 给当前进程发送一个信号,自己给自己发
/* Raise signal SIG, i.e., send SIG to yourself. */
extern int raise (int __sig) __THROW;
alarm(2) 在seconds秒后给进程发送一个SIGALRM信号
/* Schedule an alarm. In SECONDS seconds, the process will get a SIGALRM.
If SECONDS is zero, any currently scheduled alarm will be cancelled.
The function returns the number of seconds remaining until the last
alarm scheduled would have signaled, or zero if there wasn't one.
There is no return value to indicate an error, but you can set `errno'
to 0 and check its value after calling `alarm', and this might tell you.
The signal may come late due to processor scheduling. */
extern unsigned int alarm (unsigned int __seconds) __THROW;
注意,当连续使用多个alarm()时会生效最后一个
pause(2)
等待直到有一个信号来打断
/* Suspend the process until a signal arrives.
This always returns -1 and sets `errno' to EINTR.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int pause (void);
sleep(3)
阻塞进程seconds后唤醒进程,或在sleep途中被信号打断,该函数轻易不能使用,因为在有些环境下该函数就是用alarm() + pause()来封装的,当程序中有多个alarm时是可能会出错的
/* Make the process sleep for SECONDS seconds, or until a signal arrives
and is not ignored. The function returns the number of seconds less
than SECONDS which it actually slept (thus zero if it slept the full time).
If a signal handler does a `longjmp' or modifies the handling of the
SIGALRM signal while inside `sleep' call, the handling of the SIGALRM
signal afterwards is undefined. There is no return value to indicate
error, but if `sleep' returns SECONDS, it probably didn't work.
This function is a cancellation point and therefore not marked with
__THROW. */
extern unsigned int sleep (unsigned int __seconds);
可以用nanosleep()/usleep()/select()函数来代替sleep()
setitimer(2) 设置一个时钟
设置一个时钟,当old不会空时将原来时钟的信息回填到old中,将new中的时间都设置为0即可取消定时器
/* Set the timer WHICH to *NEW. If OLD is not NULL,
set *OLD to the old value of timer WHICH.
Returns 0 on success, -1 on errors. */
extern int setitimer (__itimer_which_t __which,
const struct itimerval *__restrict __new,
struct itimerval *__restrict __old) __THROW;
which表示哪个时钟,共有三种
itimerval结构体
struct itimerval {
struct timeval it_interval; /* 递减到0时将这个值赋给value且操作时原子的,即该函数自己就能构成一个 时钟周期*/
struct timeval it_value; /* 递减的就是这个值*/
};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds 微秒*/
};
abort(3)
给当前进程发送一个SIGABRT,目的是为了结束当前进程
/* Abort execution and generate a core-dump. */
extern void abort (void) __THROW __attribute__ ((__noreturn__));
system()
在可能有信号干扰的情况下使用这个函数需要注意将SIGCHLD信号block,并将SIGINT和SIGQUIT信号忽略
计算5s中能将一个数++多少次
int main(int argc, char **argv) {
time_t end;
end = time(NULL) + 5;
int64_t cnt = 0;
while (time(NULL) <= end) cnt++;
printf("%ld\n", cnt);
exit(0);
}
输出结果:
root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main
2062958242
real 0m5.401s
user 0m5.400s
sys 0m0.000s
int main(int argc, char **argv) {
alarm(5);
int64_t cnt = 0;
while (1) {
cnt++;
}
exit(0);
}
输出结果
root@VM-24-2-ubuntu:/home/ubuntu/linux# time ./main
2859820977
real 0m5.001s
user 0m4.997s
sys 0m0.004s
可以看到使用信号来实现时时间更加精确,同时cnt++执行的次数也更多了
static volatile int loop = 1; static void alarm_handler(int s) { loop = 0; } int main(int argc, char **argv) { //注意signal得再alarm前面 signal(SIGALRM, alarm_handler); int64_t cnt = 0; alarm(5); while (loop) { cnt++; } printf("%ld", cnt); exit(0); }
实现一个shell中的cat命令,要求每秒只能读取10个字符
#define CPS 10 #define BUFSIZE CPS static volatile int loop = 0; //控制时延的标志 static void alrm_handler(int s) { alarm(1); loop = 1; } /** * 实现一个cat,且每秒读取CPS个字符 */ int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage...\n"); exit(1); } int fds, fdd = 1, len, ret, pos; char buf[BUFSIZE]; //打开待读取的文件 do { fds = open(argv[1], O_RDONLY); if (fds < 0) { if (errno == EINTR) continue; perror("open():"); exit(1); } } while (fds < 0); //向终端上写数据 //首先注册信号函数 alarm(1); signal(SIGALRM, alrm_handler); while (1) { while (!loop) pause(); loop = 0
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。