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占满,电脑会变的巨卡无比,也可以修改一下代码,让它自己退出。