内核线程中获取接收到的信号
在测试开发的内核模块时,发现了一个BUG:在模块没有卸载时使用reboot命令重启系统的话,系统重启不了,查看日志发现在创建的内核线程中陷入了死循环,导致系统无法重启。检查了代码,发现产生问题的原因是当系统调用返回-EINTR(也就是被信号中断),内核线程中的循环没有退出,而是继续循环操作,这个逻辑跟业务是相符合的并没有错误。问题就在于没有检查接收到的是什么信号,如果是在系统重启时发送的信号或者执行关机时发送的信号,应该退出循环。剩下的就是找到在内核线程中获取接收的信号的方法。
在用户态获取阻塞的信号,调用的就是sigpending(),因此首先尝试调用sys_sigpending()来获取。sys_sigpending()作为系统调用是没有导出的,因此不能直接调用,但是可以通过/proc/kallsyms文件来获取sys_sigpending()的地址来调用这个函数。在我的测试机上,sys_sigpending()的地址为0xffffffff810802e0。测试代码如下所示:
[cpp] view plaincopy
/*
* fcluster.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/signal.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
static int remove_mod = 0;
static int my_sigpending(sigset_t *set)
{
int (*sigpending)(sigset_t *set);
int ret;
mm_segment_t old_fs;
sigpending = (typeof(sigpending))0xffffffff810802e0;
old_fs = get_fs();
set_fs(get_ds());
ret = sigpending(set);
set_fs(old_fs);
return ret;
}
static int thread_process(void *arg)
{
sigset_t *sigset, __sigset;
sigset = &__sigset;
allow_signal(SIGURG);
allow_signal(SIGTERM);
allow_signal(SIGKILL);
allow_signal(SIGSTOP);
allow_signal(SIGCONT);
printk(KERN_ALERT "the pid of thread_process is %d.\n", current->pid);
my_sigpending(sigset);
printk(KERN_ALERT "Before receive signal, signal map: 0x%lX.\n", sigset->sig[0]);
for ( ; !remove_mod; ) {
/* Avoid infinite loop */
msleep(1000);
if (signal_pending(current)) {
my_sigpending(sigset);
printk(KERN_ALERT "Received signal, signal map: 0x%lX.\n", sigset->sig[0]);
printk(KERN_ALERT "Receive SIGURG signal ? %s.\n",
sigismember(sigset, SIGURG) ? "true" : "false");
printk(KERN_ALERT "Receive SIGTERM signal ? %s.\n",
sigismember(sigset, SIGTERM) ? "true" : "false");
printk(KERN_ALERT "Receive SIGKILL signal ? %s.\n",
sigismember(sigset, SIGKILL) ? "true" : "false");
printk(KERN_ALERT "Receive SIGSTOP signal ? %s.\n",
sigismember(sigset, SIGSTOP) ? "true" : "false");
/* Use halt to stop the system */
printk(KERN_ALERT "Receive SIGCONT signal ? %s.\n",
sigismember(sigset, SIGCONT) ? "true" : "false");
break;
}
}
return 0;
}
static int __init fcluster_init(void)
{
kernel_thread(thread_process, NULL, CLONE_FILES);
return 0;
}
static void __exit fcluster_exit(void)
{
remove_mod = 1;
msleep(2000);
}
MODULE_LICENSE("GPL");
module_init(fcluster_init);
module_exit(fcluster_exit);
内核线程如果想接收用户终端发送的信号,必须在处理函数中调用allow_signal()来指定允许接收哪些信号。my_sigpending()是对sys_sigpending()的简单封装,用来获取当前内核线程阻塞的信号。
将上面的代码编译成内核模块,插入到系统中,打开系统日志,查看创建的内核线程的ID(我的是3278,如下图所示),然后在另一个终端中使用kill命令给创建的内核线程发送SIGTERM命令。测试结果要通过系统日志文件(/var/log/messages)来查看,如下图所示:
查看系统日志发现获取到的信号位图竟然是0!不可能啊,因为从上面的代码中可以看出只有在signal_pending()函数返回true的情况下(也就是接收到信号时),才能输出上图中的日志信息。代码很简单,关键的函数就是my_sigpending(),该函数只是对sys_sigpending()进行了简单的封装,
因此还是要从sys_sigpending()的实现中查找原因。
查看sys_sigpending()的源码,只是对do_sigpending()函数的简单封装,继续从do_sigpending()中找原因。do_sigpending()源码如下:
[cpp] view plaincopy
long do_sigpending(void __user *set, unsigned long sigsetsize)
{
long error = -EINVAL;
sigset_t pending;
if (sigsetsize > sizeof(sigset_t))
goto out;
spin_lock_irq(¤t->sighand->siglock);
sigorsets(&pending, ¤t->pending.signal,
¤t->signal->shared_pending.signal);
spin_unlock_irq(¤t->sighand->siglock);
/* Outside the lock because only this thread touches it. */
sigandsets(&pending, ¤t->blocked, &pending);
error = -EFAULT; &nb
补充:软件开发 , C++ ,
- 更多C/C++疑问解答:
- 关于c++的cout输出的问题。
- 在学校里学过C和C++,不过学的很一般,现在自学C#,会不会很难?
- 全国计算机二级C语言笔试题
- 已知某树有2个2度结点,3个3度结点,4个4度结点,问有几个叶子结点?
- c++数据结构内部排序问题,整数排序
- 2012九月计算机二级C语言全国题库,,急求急求
- 如果assert只有一个字符串作为参数,是什么意思呢?
- C语言中,哪些运算符具有左结合性,哪些具有右结合性,帮忙总结下,谢谢了!
- 为什么用结构体编写的程序输入是,0输不出来啊~~~
- 将IEEE—754的十六进制转化为十进制浮点类型,用C或C++都行,多谢各位大侠啊,非常感谢!
- 为什么这个程序求不出公式?
- 这个链表倒置的算法请大家分析下
- c语言函数库调用
- C语言unsigned int纠错
- C语言快排求解啊