14) SIGALRM
26) SIGVTALRM
27) SIGPROF
这几个信号要放在一起说,因为他们都属于闹钟信号,首先说说SIGALRM信号,man手册上说它是由alarm函数产生的,先介绍一个这个函数:
14.1 alarm
函数原型 | unsigned int alarm(unsigned int seconds); |
头文件 | unistd.h |
功能 | 设定闹钟时间,当时间到达时产生SIGALRM信号 |
参数 | [in]:seconds:设定的秒数 |
返回 | 返回调用该函数前剩余的秒数 |
该函数的返回值为调用该函数前剩余的秒数,与本次调用该函数传入的值无关。可以在产生SIGALRM信号前重复调用该函数,重新调用该函数会按照新传入的值重新及时,如果传入的seconds为0则表示取消计时。
比如第1次调用alarm函数时返回值为0,传入的seconds为5,表示定时5秒,经过2秒后重新调用alarm函数,此时返回值应为3,如果第2第调用alarm函数传入的seconds为1,则表示重新设定定时时间为1秒,1秒之后将产生SIGALRM信号。
需要特别注意的地方,alarm函数是按真实时间来算的,可以理解为比如调用了alarm(5),如果之后不再修改,那么5秒之后就会产生SIGALRM信号。
2. setitimer
alarm函数只能产生SIGALRM信号,而setitimer函数可以产生SIGALRM、SIGVTALRM和SIGPORF中的任意一个,先介绍一下setitimer函数:
函数原型 |
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); |
头文件 | sys/time.h |
功能 | 设定闹钟时间。该函数通alarm函数一样,可以在信号来之前重复调用,此举将会重设定时器。当信号到来之后会根据重装载值重新设定闹钟时间。 |
参数 |
[in]:which:定时器类型,可以选择下列3种之一 #ITIMER_REAL:按照真实时间定时,时间到达时产生SIGALRM信号 #ITIMER_VIRTUAL:按照进程在用户态占用的CPU时间定时,时间到达时产生SIGVTALRM信号 #ITIMER_PROF:按照进程在用户态和内核态占用的CPU时间定时,时间到达时产生SIGPROF信号 [in]:new_value:包含设定的定时时间,具体在后面介绍 [out]:old_value:在重载定时时间前指定的定时器的重装载值和定时器剩余值,具体在后面介绍。 |
返回 | 成功返回0,失败返回-1 |
该函数第2和第3个参数都是struct itimerval类型的指针,该结构定义如下:
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
该结构内部又包含2个struct timeval类型的结构,定义如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
其中tv_sec表示秒,tv_usec表示微秒,表示的时间是他们两个成员相加,比如当tv_sec等于1,tv_usec等于100时,表示(1s+100us)的时间。在struct itimerval结构中包含2个struct timeval结构,其中it_interval表示的是重装载时间,it_value表示设定时间,当it_value的时间到达之后,产生对应的闹钟信号,并将it_interval的值当成新的it_value(注意不是将it_interval的值赋给it_value),然后开启下一次计时。写过单片机的朋友应该非常熟悉这种操作。但是在使用过程中却会发现,通过getitimer读取出来的重装载值与设定的值会有所区别,可能会差几千个微秒,我觉得可能是系统根据实际情况做了调整。
而setitimer函数中old_value作为输出参数,虽然名字叫old_value,但它表示的意思是该定时器的当前值,其中it_interval表示的是重装载值,it_value表示的是指定的定时器剩余的时间。如果old_value传入NULL,则表示不关心该值。
3. getitimer
获取指定定时器的值,函数介绍如下:
函数原型 | int getitimer(int which, struct itimerval *curr_value); |
头文件 | sys/time.h |
功能 | 获取指定定时器的值 |
参数 |
[in]:which:通setitimer [out]:curr_value当前值,it_interval表示重装载值,it_value表示该定时器剩余的时间 |
返回 | 成功返回0,失败返回-1 |
4. 测试程序
三种信号放到了同一个测试程序,代码如下:
1 /** 2 * filename: signal_14_setitimer.c 3 * author: Suzkfly 4 * date: 2021-02-17 5 * platform: Ubuntu 6 * 操作步骤: 7 * 分别修改MODE的值为1,2,3,观察打印信息。 8 * 执行结果: 9 * 1. 当MODE为1时能产生SIGALRM信号,但是不能产生SIGVTALRM信号和SIGPROF信号, 10 * 那是因为当MODE为1时使用sleep,它将交出CPU控制权,ITIMER_VIRTUAL和 11 * ITIMER_PROF不会计算没有CPU控制权的时间,因此在sleep时,ITIMER_VIRTUAL和 12 * ITIMER_PROF不会进行倒计时。 13 * 2. 当MODE为2时,使用的是用户态的延时函数,因此ITIMER_REAL,ITIMER_VIRTUAL和 14 * ITIMER_PROF都会计时,那么也能引发相应的信号。 15 * 3. 当MODE为3时,使用的是内核态的延时函数,因此ITIMER_REAL和ITIMER_PROF 16 * 会计时,那么时间到了之后就会产生SIGALRM信号和SIGPROF信号。 17 * 18 * 注意: 19 * 当MODE为3时需要用超级用户权限执行程序 20 */ 21 #include <stdio.h> 22 #include <signal.h> 23 #include <sys/time.h> 24 #include <sys/types.h> 25 #include <sys/stat.h> 26 #include <fcntl.h> 27 28 #define MODE 3 29 30 /* 自定义信号处理函数 */ 31 void func(int sig) 32 { 33 switch (sig) { 34 case SIGALRM : 35 printf("SIGALRM signal captured. value = %d\n", sig); 36 break; 37 38 case SIGVTALRM : 39 printf("SIGVTALRM signal captured. value = %d\n", sig); 40 break; 41 42 case SIGPROF : 43 printf("SIGPROF signal captured. value = %d\n", sig); 44 break; 45 } 46 } 47 48 #if (MODE == 2) 49 /* 自定义延时函数(用户态) */ 50 static void my_delay(void) 51 { 52 volatile int i = 1000000000; 53 while (i--); 54 } 55 #endif 56 57 int main(int argc,char *argv[]) 58 { 59 void(*p)(int) = NULL; 60 int ret = 0; 61 struct itimerval new_value, cur_value; 62 int fd = 0; 63 64 printf("MODE = %d\n", MODE); 65 66 /* 注册信号 */ 67 p = signal(SIGALRM, func); 68 if (p == SIG_ERR) { 69 printf("signal SIGALRM failed\n"); 70 } 71 72 p = signal(SIGVTALRM, func); 73 if (p == SIG_ERR) { 74 printf("signal SIGVTALRM failed\n"); 75 } 76 77 p = signal(SIGPROF, func); 78 if (p == SIG_ERR) { 79 printf("signal SIGPROF failed\n"); 80 } 81 82 /* 设定时间,所有定时器都设为5秒 */ 83 new_value.it_interval.tv_sec = 5; 84 new_value.it_interval.tv_usec = 0; 85 new_value.it_value.tv_sec = 5; 86 new_value.it_value.tv_usec = 0; 87 ret = setitimer(ITIMER_REAL, &new_value, NULL); 88 if (ret == -1) { 89 printf("setitimer ITIMER_REAL failed\n"); 90 return 0; 91 } 92 ret = setitimer(ITIMER_VIRTUAL, &new_value, NULL); 93 if (ret == -1) { 94 printf("setitimer ITIMER_VIRTUAL failed\n"); 95 return 0; 96 } 97 ret = setitimer(ITIMER_PROF, &new_value, NULL); 98 if (ret == -1) { 99 printf("setitimer ITIMER_PROF failed\n"); 100 return 0; 101 } 102 103 #if (MODE == 3) 104 /* 打开自己编写的设备 */ 105 fd = open("/dev/my_module", O_RDWR); 106 if (fd < 0) { 107 printf("open failed\n"); 108 return 0; 109 } 110 #endif 111 112 while (1) { 113 #if (MODE == 1) 114 sleep(1); /* 休眠,将交出CPU使用权 */ 115 #elif (MODE == 2) 116 my_delay(); /* 用户态延时 */ 117 #elif (MODE == 3) 118 ioctl(fd, 123, 456); /* 内核态延时 */ 119 #endif 120 getitimer(ITIMER_REAL, &cur_value); 121 printf("REAL.it_value.tv_sec = %ld\n", cur_value.it_value.tv_sec); 122 printf("REAL.it_value.tv_usec = %ld\n\n", cur_value.it_value.tv_usec); 123 124 getitimer(ITIMER_VIRTUAL, &cur_value); 125 printf("VIRTUAL.it_value.tv_sec = %ld\n", cur_value.it_value.tv_sec); 126 printf("VIRTUAL.it_value.tv_usec = %ld\n\n", cur_value.it_value.tv_usec); 127 128 getitimer(ITIMER_PROF, &cur_value); 129 printf("PROF.it_value.tv_sec = %ld\n", cur_value.it_value.tv_sec); 130 printf("PROF.it_value.tv_usec = %ld\n\n", cur_value.it_value.tv_usec); 131 132 printf("##############################\n"); /* 分隔线 */ 133 } 134 135 return 0; 136 }
测试结果及分析:
当MODE为1时,结果如下:
结合程序,当MODE为1时,在while(1)中调用了sleep,sleep将使进程交出CPU的控制权,那么此时只有ITIMER_REAL会计算时间,根据getitime的结果也可以看出来,只有ITIMER_REAL的时间在变少,ITIMER_VIRTUAL和ITIMER_PROF的时间都没有变少。当ITIMER_REAL时间到0时引发SIGALRM信号,然后开启下一轮计时。
当MODE为2时的结果:
当MODE为2时,使用了一个自己在用户态写的延时函数,该函数会在用户态占用CPU,因此ITIMER_REAL、ITIMER_VIRTUAL和ITIMER_PROF三个时间都会计时,并产生响应的信号。
当MODE为3时情况就比较特殊了,程序打开了一个文件:"/dev/my_module",得到一个文件描述符,然后再while(1)中调用了ioctl(fd, 123, 456);实际上这是我在内核态实现的一个延时函数,需要先将驱动加载进内核,并且以超级用户权限执行程序,否则无法成功打开文件。
驱动程序如下:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/cdev.h> 4 #include <linux/fs.h> 5 #include <linux/io.h> 6 #include <linux/miscdevice.h> 7 8 9 static int my_module_open (struct inode *p_inode, struct file *p_file) 10 { 11 12 return 0; 13 } 14 15 /* 当调用ioctl时进行延时 */ 16 static long my_module_ioctl (struct file *p_file, unsigned int cmd, unsigned long arg) 17 { 18 volatile int i = 1000000000; 19 20 printk("cmd = %u\n", cmd); 21 printk("arg = %ul\n", arg); 22 23 while (i--); 24 25 return 0; 26 } 27 28 static struct file_operations fops = { 29 .open = my_module_open, 30 .unlocked_ioctl = my_module_ioctl, 31 }; 32 33 static struct miscdevice my_module_dev = { 34 .minor = MISC_DYNAMIC_MINOR, 35 .name = "my_module", 36 .fops = &fops, 37 }; 38 39 static __init int my_module_init (void) 40 { 41 misc_register(&my_module_dev); 42 printk("my module init\n"); 43 44 return 0; 45 } 46 47 static __exit void my_module_exit (void) 48 { 49 misc_deregister(&my_module_dev); 50 printk("my module exit\n"); 51 } 52 53 module_init(my_module_init); 54 module_exit(my_module_exit); 55 56 MODULE_LICENSE("GPL");
这份代码就只实现了一个ioctl函数,其具体的延时时间不能确定。
Makefile如下:
1 ifeq ($(KERNELRELEASE),) 2 KVERSION = $(shell uname -r) 3 all: 4 make -C /lib/modules/$(KVERSION)/build M=$(shell pwd) modules 5 clean: 6 make -C /lib/modules/$(KVERSION)/build M=$(shell pwd) clean modules_install 7 $(MAKE) -C $(KERNELDIR)M=$(PWD) modules_install 8 else 9 obj-m :=my_module.o 10 endif
使用Makefile时要注意,由于复制粘贴的原因,该文件内的tab键被替换成了空格,在实际使用的时候一定要将缩进的空格键换回tab键,否则执行make的时候会报错。
执行make,在该目录下会生成驱动文件,my_module.ko
执行sudo insmod my_module.ko命令,加载驱动文件,此时在/dev/目录下将会出现名为“my_module”的文件
之后再以超级用户权限执行MODE为3的应用程序,结果如下:
从结果可以看出来产生了SIGPROF信号和SIGALRM信号,ITIMER_VIRTUAL定时器的值一直没有变,那是因为ITIMER_VIRTUAL只会计算用户态占用CPU的时间,而不会计算内核态占用CPU的时间,而ITIMER_PROF既会计算用户态占用CPU的时间,也会计算内核态占用CPU的时间。
提示:
如过调用了本例子中的内核延时函数(MODE为3),那么它会直接将CPU占满,电脑会变的巨卡无比,也可以修改一下代码,让它自己退出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
2020-02-19 查看内核打印信息指令dmesg
2019-02-19 DB9接口定义