从Linux内核模块向应用发信号

从Linux内核模块向应用发信号

有一个客户有这样的需求,要从设备驱动/内核模块向应用程序发信号,我在网上找了资料,并做了一个测试用例,证明是可行的。 这里的关键是内核模块要找到相应的应用程序的进程号, 这可以通过 ioctl 函数把应用的进程号传给内核模块。 另外, 内核模块发送信号的API 是 send_sig_info。 应用程序要注册信号处理函数, 这跟常规的做法没有区别。

这是应用程序的源码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static sem_t event_sem;
static volatile sig_atomic_t interested_event = 0;

void sig_handler_event1(int sig)
{
        interested_event = 1;
        sem_post(&event_sem);
}

static void * event_handler_thread_func()
{
        printf("in %s line %d\n", __func__, __LINE__);
        while(1){
                sem_wait(&event_sem);
                if (interested_event){
                        printf("%s,%d, received interested_event signal.\n",__func__, __LINE__);
                        interested_event = 0;
                }
        }
        pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
        pthread_t event_thread;
        if (pthread_create(&event_thread, NULL, event_handler_thread_func, NULL) != 0){
                printf("Thread create failed%s.\n", strerror(errno));
                exit(1);
        }
        sem_init(&event_sem, 0, 0);

        struct sigaction usr_action;
        sigset_t block_mask;
        sigfillset (&block_mask);
        usr_action.sa_handler = sig_handler_event1;
        usr_action.sa_mask = block_mask;//block all signal inside signal handler.
        usr_action.sa_flags = SA_NODEFER;//do not block SIGUSR1 within sig_handler_int.
        printf ("handle signal %d\n", SIGRTMIN+1);
        sigaction (SIGRTMIN+1, &usr_action, NULL);
        int fd = open("/dev/lkm_example", O_RDWR);
        int my_pid = getpid();

        printf("in %s line %d\n", __func__, __LINE__);
        ioctl(fd, 0x111, &my_pid);
        close(fd);
        while(1)
                sleep(5);
}

源码的主要部分是用sigaction 注册了针对信号 SIGRTMIN+1 的信号处理函数 sig_handler_event1, 它收到信号后,释放信号量event_sem,使得阻塞在该信号量上的线程 event_handler_thread_func 能够执行:

void sig_handler_event1(int sig)
{
        interested_event = 1;
        sem_post(&event_sem);
}

这是 event_handler_thread_func 的代码:

static void * event_handler_thread_func()
{
        printf("in %s line %d\n", __func__, __LINE__);
        while(1){
                sem_wait(&event_sem);
                if (interested_event){
                        printf("%s,%d, received interested_event signal.\n",__func__, __LINE__);
                        interested_event = 0;
                }
        }
        pthread_exit(NULL);
}

另外, main 函数打开了设备 /dev/lkm_example, 这是下面要讲的内核模块对应的设备, 并用ioctl 函数,把自己的PID传给了内核模块。

这是内核模块的源码:

/*
 * sending signal from kernel module to user space. Usage:

insmod ./lkm_example.ko
mknod /dev/lkm_example c 249 0
./test1 &
echo 0 > /dev/lkm_example 

 */

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yuwen");
MODULE_DESCRIPTION("A Linux module that sends signal to userspace.");
MODULE_VERSION("0.01");

#define DEVICE_NAME "lkm_example"
#define EXAMPLE_MSG "Hello, World!\n"
#define MSG_BUFFER_LEN 15

typedef enum{
        THIS_MODULE_IOCTL_SET_OWNER = 0x111,
}MODULE_IOCTL_CMD;

#if 1
#undef SIGRTMIN
#define SIGRTMIN 34
#endif

/* Prototypes for device functions */
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg);

static void send_signal(int sig_num);

static int major_num;
static int device_open_count = 0;
static char msg_buffer[MSG_BUFFER_LEN];
static char *msg_ptr;

/* This structure points to all of the device functions */
static struct file_operations file_ops = {
        .read = device_read,
        .write = device_write,
        .open = device_open,
        .release = device_release,
        .unlocked_ioctl = device_ioctl,
        .compat_ioctl = device_ioctl
};

static int owner = 0;
struct task_struct *current_task;

static void send_signal(int sig_num)
{
       struct siginfo info;
       int ret;
       
       if (owner == 0)
               return;
       printk("%s,%d.sending signal %d to owner %d\n",__func__, __LINE__, sig_num, owner);

       memset(&info, 0, sizeof(struct siginfo));
       info.si_signo = sig_num;
       info.si_code = 0;
       info.si_int = 1234;
       if (current_task == NULL){
               rcu_read_lock();
               current_task = pid_task(find_vpid(owner), PIDTYPE_PID);
               rcu_read_unlock();
       }
       ret = send_sig_info(sig_num, &info, current_task);
       if (ret < 0) {
               printk("error sending signal\n");
       }
}



/* When a process reads from our device, this gets called. */
static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset) {
	    /* not implemented yet */ 
        return 0;
}

/* Called when a process tries to write to our device */
static ssize_t device_write(struct file *flip, const char *buffer, size_t len, loff_t *offset) {
        /* This is a read-only device */
        printk(KERN_ALERT "This operation is not supported.\n");
        send_signal(SIGRTMIN+1);
        /*        return -EINVAL;*/
        return len;
}

/* Called when a process opens our device */
static int device_open(struct inode *inode, struct file *file) {
        /* If device is open, return busy */
        if (device_open_count) {
                return -EBUSY;
        }
        device_open_count++;
        try_module_get(THIS_MODULE);
        return 0;
}

/* Called when a process closes our device */
static int device_release(struct inode *inode, struct file *file) {
        /* Decrement the open counter and usage count. Without this, the module would not unload. */
        device_open_count--;
        module_put(THIS_MODULE);
        return 0;
}

static int __init lkm_example_init(void) {
        /* Fill buffer with our message */
        strncpy(msg_buffer, EXAMPLE_MSG, MSG_BUFFER_LEN);
        /* Set the msg_ptr to the buffer */
        msg_ptr = msg_buffer;
        /* Try to register character device */
        major_num = register_chrdev(0, "lkm_example", &file_ops);
        if (major_num < 0) {
                printk(KERN_ALERT "Could not register device: %d\n", major_num);
                return major_num;
        } else {
                printk(KERN_INFO "lkm_example module loaded with device major number %d\n", major_num);
                return 0;
        }
}

static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
        if(cmd == THIS_MODULE_IOCTL_SET_OWNER) {
                printk("%s, owner pid %d\n", __func__, owner);
                if(copy_from_user(&owner, (int *)arg, sizeof(int))) {/* ??? */
                         return -EFAULT;
                }
                current_task = NULL;
                return 0;
        } else
                return -ENOIOCTLCMD;
}




        
static void __exit lkm_example_exit(void) {
        /* Remember — we have to clean up after ourselves. Unregister the character device. */
        unregister_chrdev(major_num, DEVICE_NAME);
        printk(KERN_INFO "Goodbye, World!\n");
}

/* Register module functions */
module_init(lkm_example_init);
module_exit(lkm_example_exit);

在模块初始化函数 lkm_example_init 里, 我们创建了一个字符设备 /dev/lkm_example, 模块加载后,要根据

 printk(KERN_INFO "lkm_example module loaded with device major number %d\n", major_num);

的输出, 创建一个字符设备(假设major_num是249):

mknod /dev/lkm_example c 249 0

该模块还定义了ioctl 函数:

static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
        if(cmd == THIS_MODULE_IOCTL_SET_OWNER) {
                printk("%s, owner pid %d\n", __func__, owner);
                if(copy_from_user(&owner, (int *)arg, sizeof(int))) {
                         return -EFAULT;
                }
                current_task = NULL;
                return 0;
        } else
                return -ENOIOCTLCMD;
}

把调用进程的ID号保存到全局变量owner, send_signal 会把ID号转化为task_struct 结构,供 send_sig_info 使用:

       if (current_task == NULL){
               rcu_read_lock();
               current_task = pid_task(find_vpid(owner), PIDTYPE_PID);
               rcu_read_unlock();
       }

为了触发信号, 我们在驱动的write 方法里调用 send_signal:

static ssize_t device_write(struct file *flip, const char *buffer, size_t len, loff_t *offset) {
        /* This is a read-only device */
        printk(KERN_ALERT "This operation is not supported.\n");
        send_signal(SIGRTMIN+1);
        /*        return -EINVAL;*/
        return len;
}

这样,当我们往设备 /dev/lkm_example 里写数据的时候,就会触发信号:

echo 1 > /dev/lkm_example

这是全部命令行操作:

insmod ./lkm_example.ko
mknod /dev/lkm_example c 249 0
./test1 &
echo 1 > /dev/lkm_example

此时会看到应用程序test1 的输出:

received interested_event signal.

你可能感兴趣的:(Linux,开发)