有一个客户有这样的需求,要从设备驱动/内核模块向应用程序发信号,我在网上找了资料,并做了一个测试用例,证明是可行的。 这里的关键是内核模块要找到相应的应用程序的进程号, 这可以通过 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.