Signal is one way that Kenerl can notify User Space application that something happened.
Linux Version: Ubuntu 18.04
This demo includes:
1. kernel module + Makefile
2. application
Application will register it's task_id to kernel module by ioctl(). Kernel module write() API will send a signal to application. Application will register a handler to handle the signal from kernel. One char device is the bridge between kernel module and application.
First, it is kernel module -- signal.c
/*
* sending signal from kernel module to user space. Usage:
*/
#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>
#include <linux/sched/signal.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alex Xie");
MODULE_DESCRIPTION("A Linux module that sends signal to userspace.");
MODULE_VERSION("0.01");
#define DEVICE_NAME "signal_example"
#define EXAMPLE_MSG "Hello, World!\n"
#define MSG_BUFFER_LEN 15
typedef enum{
THIS_MODULE_IOCTL_SET_OWNER = 0x111,
}MODULE_IOCTL_CMD;
#undef SIGRTMIN
#define SIGRTMIN 34
/* 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;
/* 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;
static void send_signal(int sig_num)
{
struct kernel_siginfo info;
struct task_struct *current_task=NULL;
int ret;
printk(KERN_INFO"signal_example: %s\n",__func__);
if (owner == 0)
return;
printk(KERN_INFO"%s,%d.signal_example: sending signal %d to owner %d\n",__func__, __LINE__, sig_num, owner);
memset(&info, 0, sizeof(struct kernel_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();
}
printk(KERN_INFO"signal_example:send signal to task %p\n",current_task);
ret = send_sig_info(sig_num, &info, current_task);
if (ret < 0) {
printk(KERN_INFO"signal_example:error sending signal\n");
}
printk(KERN_INFO"signal_example:send signal to task %p done\n",current_task);
}
/* 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 "signal_example: send signal to user space.\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 */
printk(KERN_INFO"signal_example:%s,%d\n",__func__,device_open_count);
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 signal_example_init(void) {
/* Try to register character device */
major_num = register_chrdev(0, "signal_example", &file_ops);
if (major_num < 0) {
printk(KERN_ALERT "signal_example:Could not register device: %d\n", major_num);
return major_num;
} else {
printk(KERN_INFO "signal_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)
{
printk(KERN_INFO "signal_example:%s cmd=0x%x,arg=0x%lx\n", __func__,cmd,arg);
if(cmd == THIS_MODULE_IOCTL_SET_OWNER) {
printk(KERN_INFO"%s, signal_example:owner pid at 0x%lx\n", __func__, arg);
#if 1
if(copy_from_user(&owner, (int *)arg, sizeof(int))) {
printk(KERN_INFO"signal_example:copy from user failed\n");
return -EFAULT;
}
#else
__get_user(owner,(int*)arg);
#endif
printk(KERN_INFO"signal_example: owner is %d\n",owner);
return 0;
} else
return 0;
}
static void __exit signal_example_exit(void) {
/* Remember — we have to clean up after ourselves. Unregister the character device. */
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "signal_example:Goodbye, World!\n");
}
/* Register module functions */
module_init(signal_example_init);
module_exit(signal_example_exit);
Then Kernel module's Makefile
obj-m := signal.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
How to make Kernel module:
Put Makefile and signal.c in the same foler then make. As following:
Application code -- app.c :
#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 volatile sig_atomic_t interested_event = 0;
void sig_handler_event1(int sig)
{
interested_event = 1;
printf("received signal_example kernel signal\n");
}
int main(int argc, char *argv[])
{
int fd=0;
int my_pid;
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);
fd = open("/dev/signal_example", O_RDWR);
printf("fd is %d\n",fd);
my_pid = getpid();
printf("in %s my_pid is %d\n", __func__, my_pid);
ioctl(fd, 0x111, &my_pid);
close(fd);
while(1){
sleep(60);
printf("I am alive\n");
}
}
Build application:
>gcc -o app app.c
Now we can start to test them.
1. insmod kernel module
2. create node /dev/signal_example with major device id 238 -- 238 is from the kernel log
3. start application
And we can see application send its task_id(5682) to kernel module by ioctl(), here is kernel log:
4. trigger signal
this operation will trigger device_write() of kernel module, then trigger send_signal() which will send signal to application. Here is the log:
as soon as kernel module send signal to application, application's handler will handle it as:
Here you can see 'received signal_example kernel signal' . This is from the handler.
Attention, we run all as root.