Author: Daoge, a 10 + year embedded development veteran, focusing on: C/C + +, embedded and Linux.
Pay attention to the official account below, reply to books, get classic books in Linux and embedded field. Reply to [PDF] to obtain all original articles (PDF format).
catalogue
Other people's experience, our ladder!
Hello, I'm brother Dao. The technical knowledge I'll explain to you today is: [how an interrupt program sends a signal to the application layer].
Several articles shared recently are more basic, about character device drivers and interrupt handlers.
Perhaps such technology is not used in modern projects, but ten thousand tall buildings rise from the ground.
Only after understanding these most basic knowledge points, and then looking at the advanced gadgets evolved, can we have a sense of gain step by step.
Without these basic links, many deep-seated things will feel like castles in the air.
It's like studying the Linux kernel. If you start from Linux 4 x/5. After studying the X kernel version, you can see a lot of "historical" code.
These codes witness the development history of Linux step by step. Some people even study the kernel source code of Linux version 0.11, because many basic ideas are the same.
Today's article mainly focuses on code examples and combines the previous two knowledge points:
In the interrupt processing function, a signal is sent to the application layer to notify the application layer to process the interrupt service in response.
Driver
Sample code overview
All operations are completed in ~ / tmp/linux-4.15/drivers directory.
First create the driver module Directory:
$ cd ~/tmp/linux-4.15/drivers $ mkdir my_driver_interrupt_signal $ touch my_driver_interrupt_signal.c
The contents of the document are as follows:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/ctype.h> #include <linux/device.h> #include <linux/cdev.h> #include <asm/siginfo.h> #include <linux/pid.h> #include <linux/uaccess.h> #include <linux/sched/signal.h> #include <linux/pid_namespace.h> #include <linux/interrupt.h> // interrupt number #define IRQ_NUM 1 // Defines the ID of the driver, which is used in the interrupt processing function to determine whether it needs to be processed #define IRQ_DRIVER_ID 1234 // Equipment name #define MYDEV_NAME "mydev" // Driver data structure struct myirq { int devid; }; struct myirq mydev ={ IRQ_DRIVER_ID }; #define KBD_DATA_REG 0x60 #define KBD_STATUS_REG 0x64 #define KBD_SCANCODE_MASK 0x7f #define KBD_STATUS_MASK 0x80 // Equipment class static struct class *my_class; // Used to save the device struct cdev my_cdev; // Used to save the equipment number int mydev_major = 0; int mydev_minor = 0; // It is used to save who to send signals to. The application sets its own process ID through ioctl. static int g_pid = 0; // Used to send signals to applications static void send_signal(int sig_no) { int ret; struct siginfo info; struct task_struct *my_task = NULL; if (0 == g_pid) { // This indicates that the application has not set its own PID printk("pid[%d] is not valid! \n", g_pid); return; } printk("send signal %d to pid %d \n", sig_no, g_pid); // Construct signal structure memset(&info, 0, sizeof(struct siginfo)); info.si_signo = sig_no; info.si_errno = 100; info.si_code = 200; // Obtain your own task information and use the RCU lock rcu_read_lock(); my_task = pid_task(find_vpid(g_pid), PIDTYPE_PID); rcu_read_unlock(); if (my_task == NULL) { printk("get pid_task failed! \n"); return; } // Send signal ret = send_sig_info(sig_no, &info, my_task); if (ret < 0) { printk("send signal failed! \n"); } } //Interrupt handling function static irqreturn_t myirq_handler(int irq, void * dev) { struct myirq mydev; unsigned char key_code; mydev = *(struct myirq*)dev; // Check the device id and process it only when it is equal if (IRQ_DRIVER_ID == mydev.devid) { // Read keyboard scan code key_code = inb(KBD_DATA_REG); if (key_code == 0x01) { printk("EXC key is pressed! \n"); send_signal(SIGUSR1); } } return IRQ_HANDLED; } // Driver module initialization function static void myirq_init(void) { printk("myirq_init is called. \n"); // Register interrupt handler if(request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev)!=0) { printk("register irq[%d] handler failed. \n", IRQ_NUM); return -1; } printk("register irq[%d] handler success. \n", IRQ_NUM); } // Called when the application opens the device static int mydev_open(struct inode *inode, struct file *file) { printk("mydev_open is called. \n"); return 0; } static long mydev_ioctl(struct file* file, unsigned int cmd, unsigned long arg) { void __user *pArg; printk("mydev_ioctl is called. cmd = %d \n", cmd); if (100 == cmd) { // Describes the PID of the application setup process pArg = (void *)arg; if (!access_ok(VERIFY_READ, pArg, sizeof(int))) { printk("access failed! \n"); return -EACCES; } // Copy user space data to kernel space if (copy_from_user(&g_pid, pArg, sizeof(int))) { printk("copy_from_user failed! \n"); return -EFAULT; } } return 0; } static const struct file_operations mydev_ops={ .owner = THIS_MODULE, .open = mydev_open, .unlocked_ioctl = mydev_ioctl }; static int __init mydev_driver_init(void) { int devno; dev_t num_dev; printk("mydev_driver_init is called. \n"); // Register interrupt handler if(request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev)!=0) { printk("register irq[%d] handler failed. \n", IRQ_NUM); return -1; } // Dynamically apply for the equipment number (if it is more rigorous, the return value of the function should be checked) alloc_chrdev_region(&num_dev, mydev_minor, 1, MYDEV_NAME); // Get master device number mydev_major = MAJOR(num_dev); printk("mydev_major = %d. \n", mydev_major); // Create device class my_class = class_create(THIS_MODULE, MYDEV_NAME); // Create device node devno = MKDEV(mydev_major, mydev_minor); // Initialize cdev structure cdev_init(&my_cdev, &mydev_ops); // Register character device cdev_add(&my_cdev, devno, 1); // Create device node device_create(my_class, NULL, devno, NULL, MYDEV_NAME); return 0; } static void __exit mydev_driver_exit(void) { printk("mydev_driver_exit is called. \n"); // Delete device node cdev_del(&my_cdev); device_destroy(my_class, MKDEV(mydev_major, mydev_minor)); // Release device class class_destroy(my_class); // Logout equipment number unregister_chrdev_region(MKDEV(mydev_major, mydev_minor), 1); // Logoff interrupt handler free_irq(IRQ_NUM, &mydev); } MODULE_LICENSE("GPL"); module_init(mydev_driver_init); module_exit(mydev_driver_exit);
The above code mainly does two things:
-
Handler for registering interrupt number 1: myirq_handler();
-
Create device node / dev/mydev;
The interrupt number 1 here is the keyboard interrupt.
Because it is a shared interrupt, when the keyboard is pressed, the operating system will call all interrupt handling functions in turn, including the function registered by our driver.
Several key codes related to interrupt processing are as follows:
//Interrupt handling function static irqreturn_t myirq_handler(int irq, void * dev) { ... } // Driver module initialization function static void myirq_init(void) { ... request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev); ... }
In the interrupt processing function, the goal is to send the signal SIGUSR1 to the application layer, so the driver needs to know the process number (PID) of the application.
According to the previous article Linux driver practice: how does the driver send [signal] to the application? , the application must actively tell its PID to the driver module. This can be achieved through the write or ioctl functions,
The relevant codes used by the driver to receive PID are:
static long mydev_ioctl(struct file* file, unsigned int cmd, unsigned long arg) { ... if (100 == cmd) { pArg = (void *)arg; ... copy_from_user(&g_pid, pArg, sizeof(int)); } }
Knowing the PID of the application, the driver can send a signal when an interrupt occurs (press the ESC key on the keyboard):
static void send_signal(int sig_no) { struct siginfo info; ... send_sig_info(...); } static irqreturn_t myirq_handler(int irq, void * dev) { ... send_signal(SIGUSR1); }
Makefile file
ifneq ($(KERNELRELEASE),) obj-m := my_driver_interrupt_signal.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *.ko *.mod.* modules.* Module.* $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean endif
Compilation and testing
First, check all drivers of interrupt 1 before loading the driver module:
Take another look at the equipment number:
$ cat /proc/devices
Because the driver registration dynamically requests the system to allocate when creating the device node.
According to the previous articles, the system will generally assign 244 to us, which does not exist at the moment.
Compile and load driver module:
$ make $ sudo insmod my_driver_interrupt_signal.ko
First, take a look at the output information of dmesg:
Then look at the interrupt driver:
You can see that our driver (mydev) has been registered on the far right of interrupt 1.
Finally, let's take a look at the equipment nodes:
The driver module is ready. Here is the application.
application program
The main functions of the application are two parts:
-
Tell your PID to the driver through ioctl function;
-
The processing function of the registration signal SIGUSR1;
Sample code overview
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <fcntl.h> #include <sys/ioctl.h> #include <signal.h> char *dev_name = "/dev/mydev"; // Signal processing function static void signal_handler(int signum, siginfo_t *info, void *context) { // Print the received signal value printf("signal_handler: signum = %d \n", signum); printf("signo = %d, code = %d, errno = %d \n", info->si_signo, info->si_code, info->si_errno); } int main(int argc, char *argv[]) { int fd, count = 0; int pid = getpid(); // Open GPIO if((fd = open(dev_name, O_RDWR | O_NDELAY)) < 0){ printf("open dev failed! \n"); return -1; } printf("open dev success! \n"); // Register signal processing function struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_sigaction = &signal_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &sa, NULL); // set PID printf("call ioctl. pid = %d \n", pid); ioctl(fd, 100, &pid); // Dead cycle, waiting for signal to be received while (1) sleep(1); // Turn off the device close(fd); }
At the end of the application, there is a while(1) loop. Because the driver will send a signal only when the ESC key on the keyboard is pressed, the application needs to be alive all the time.
Compilation and testing
Open a new interrupt window to compile and execute the application:
$ gcc my_interrupt_singal.c -o my_interrupt_singal $ sudo ./my_interrupt_singal open dev success! call ioctl. pid = 12907 // Here we enter the while loop
Since the application calls the open and ioctl functions, the two corresponding functions in the driver will be executed.
This can be seen from the output information of the dmesg command:
At this time, press the ESC key on the keyboard, and the following information will be printed in the driver:
Note: the driver captures the ESC key on the keyboard and sends a signal to the application.
In the terminal window executing the application, you can see the following output information:
Description: the application received the signal from the driver!
------ End ------
The test code and related documents in this article have been put on the network disk.
In the official account [IOT Internet of things] background reply keyword: 1220, you can get download address.
thank you!
Recommended reading
[3] The underlying debugging principle of gdb is so simple
[4] Is inline assembly terrible? Finish reading this article and end it!
Other albums: Selected articles,Application design,Internet of things, C language.
The official account of star standard is the first time to read articles.