当前位置:编程学习 > C/C++ >>

内核线程中获取接收到的信号

在测试开发的内核模块时,发现了一个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++ ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,