從Linux內核模塊嚮應用發信號

從Linux內核模塊嚮應用發信號

有一個客戶有這樣的需求,要從設備驅動/內核模塊嚮應用程序發信號,我在網上找了資料,並做了一個測試用例,證明是可行的。 這裏的關鍵是內核模塊要找到相應的應用程序的進程號, 這可以通過 ioctl 函數把應用的進程號傳給內核模塊。 另外, 內核模塊發送信號的API 是 send_sig_info。 應用程序要註冊信號處理函數, 這跟常規的做法沒有區別。

這是應用程序的源碼:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ioctl.h>

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 <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#include <linux/sched.h>
#include <asm/siginfo.h>
#include <linux/pid_namespace.h>
#include <linux/pid.h>

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.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章