成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux驅動實踐:驅動程序如何發送【信號】給應用程序?

開發 前端
在上一篇文章中,我們討論的是:在應用層如何發送指令來控制驅動層的 GPIOLinux驅動實踐:如何編寫【 GPIO 】設備的驅動程序。今天我為大伙兒解說的技術知識點是:【驅動層中,如何發送信號給應用程序】

[[438633]]

別人的經驗,我們的階梯!

大家好,我是道哥,今天我為大伙兒解說的技術知識點是:【驅動層中,如何發送信號給應用程序】。

在上一篇文章中,我們討論的是:在應用層如何發送指令來控制驅動層的 GPIOLinux驅動實踐:如何編寫【 GPIO 】設備的驅動程序?。控制的方向是從應用層到驅動層:

那么,如果想讓程序的執行路徑從下往上,也就是從驅動層傳遞到應用層,應該如何實現呢?

 

最容易、最簡單的方式,就是通過發送信號!

這篇文章繼續以完整的代碼實例來演示如何實現這個功能。

kill 命令和信號

使用 kill 命令發送信號

關于 Linux 操作系統的信號,每位程序員都知道這個指令:使用 kill 工具來“殺死”一個進程:

  1. $ kill -9 <進程的 PID> 

這個指令的功能是:向指定的某個進程發送一個信號 9,這個信號的默認功能是:是停止進程。

雖然在應用程序中沒有主動處理這個信號,但是操作系統默認的處理動作是終止應用程序的執行。

除了發送信號 9,kill 命令還可以發送其他的任意信號。

在 Linux 系統中,所有的信號都使用一個整型數值來表示,可以打開文件 /usr/include/x86_64-linux-gnu/bits/signum.h(你的系統中可能位于其它的目錄) 查看一下,比較常見的幾個信號是:

  1. /* Signals.  */ 
  2. #define SIGINT      2   /* Interrupt (ANSI).  */ 
  3. #define SIGKILL     9   /* Kill, unblockable (POSIX).  */ 
  4. #define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */ 
  5. #define SIGSEGV     11  /* Segmentation violation (ANSI).  */ 
  6. #define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */ 
  7. ... 
  8. ... 
  9. #define SIGSYS      31  /* Bad system call.  */ 
  10. #define SIGUNUSED   31 
  11.  
  12. #define _NSIG       65  /* Biggest signal number + 1 
  13.                    (including real-time signals).  */ 
  14.  
  15. /* These are the hard limits of the kernel.  These values should not be 
  16.    used directly at user level.  */ 
  17. #define __SIGRTMIN  32 
  18. #define __SIGRTMAX  (_NSIG - 1) 

信號 9 對應著 SIGKILL,而信號11(SIGSEGV)就是最令人討厭的Segmentfault!

這里還有一個地方需要注意一下:實時信號和非實時信號,它倆的主要區別是:

  1. 非實時信號:操作系統不確保應用程序一定能接收到(即:信號可能會丟失);
  2. 實時信號:操作系統確保應用程序一定能接收到;

如果我們的程序設計,通過信號機制來完成一些功能,那么為了確保信號不會丟失,肯定是使用實時信號的。

從文件 signum.h 中可以看到,實時信號從 __SIGRTMIN(數值:32) 開始。

多線程中的信號

我們在編寫應用程序時,雖然沒有接收并處理 SIGKILL 這個信號,但是一旦別人發送了這個信號,我們的程序就被操作系統停止掉了,這是默認的動作。

那么,在應用程序中,應該可以主動聲明接收并處理指定的信號,下面就來寫一個最簡單的實例。

在一個應用程序中,可能存在多個線程;

當有一個信號發送給此進程時,所有的線程都可能接收到,但是只能有一個線程來處理;

在這個示例中,只有一個主線程來接收并處理信號;

信號注冊和處理函數

按照慣例,所有應用程序文件都創建在 ~/tmp/App 目錄中。

  1. // 文件:tmp/App/app_handle_signal/app_handle_signal.c 
  2.  
  3. #include <stdio.h> 
  4. #include <stdlib.h> 
  5. #include <unistd.h> 
  6. #include <sys/ioctl.h> 
  7. #include <signal.h> 
  8.  
  9. // 信號處理函數 
  10. static void signal_handler(int signum, siginfo_t *info, void *context) 
  11.     // 打印接收到的信號值 
  12.     printf("signal_handler: signum = %d \n", signum); 
  13.  
  14. int main(void) 
  15.     int count = 0; 
  16.     // 注冊信號處理函數 
  17.     struct sigaction sa; 
  18.     sigemptyset(&sa.sa_mask); 
  19.     sa.sa_sigaction = &signal_handler; 
  20.     sa.sa_flags = SA_SIGINFO; 
  21.     sigaction(SIGUSR1, &sa, NULL); 
  22.     sigaction(SIGUSR2, &sa, NULL); 
  23.  
  24.     // 一直循環打印信息,等待接收發信號 
  25.     while (1) 
  26.     { 
  27.         printf("app_handle_signal is running...count = %d \n", ++count); 
  28.         sleep(5); 
  29.     } 
  30.  
  31.     return 0; 

這個示例程序接收的信號是 SIGUSR1 和 SIGUSR2,也就是數值 10 和 12。

編譯、執行:

  1. $ gcc app_handle_signal.c -o app_handle_signal 
  2. $ ./app_handle_signal 

此時,應用程序開始執行,等待接收信號。

在另一個終端中,使用kill指令來發送信號SIGUSR1或者 SIGUSR2。

kill 發送信號,需要知道應用程序的 PID,可以通過指令: ps -au | grep app_handle_signal 來查看。

其中的15428就是進程的 PID。

執行發送信號SIGUSR1指令:

  1. $ kill -10 15428 

此時,在應用程序的終端窗口中,就能看到下面的打印信息:

說明應用程序接收到了 SIGUSR1 這個信號!

注意:我們是使用kill命令來發送信號的,kill 也是一個獨立的進程,程序的執行路徑如下: 

在這個執行路徑中,我們可控的部分是應用層,至于操作系統是如何接收kill的操作,然后如何發送信號給 app_handle_signal 進程的,我們不得而知。

下面就繼續通過示例代碼來看一下如何在驅動層主動發送信號。

驅動程序代碼示例:發送信號

功能需求

在剛才的簡單示例中,可以得出下面這些信息:

  1. 信號發送方:必須知道向誰[PID]發送信號,發送哪個信號;
  2. 信號接收方:必須定義信號處理函數,并且向操作系統注冊:接收哪些信號;

發送方當然就是驅動程序了,在示例代碼中,繼續使用 SIGUSR1 信號來測試。

那么,驅動程序如何才能知道應用程序的PID呢?可以讓應用程序通過oictl函數,把自己的PID主動告訴驅動程序:

驅動程序

這里的示例代碼,是在上一篇文章的基礎上修改的,改動部分的內容,使用宏定義 MY_SIGNAL_ENABLE 控制起來,方便查看和比較。

以下所有操作的工作目錄,都是與上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。

  1. $ cd ~/tmp/linux-4.15/drivers/ 
  2. $ mkdir my_driver_signal 
  3. $ cd my_driver_signal 
  4. $ touch my_driver_signal.c 

my_driver_signal.c 文件的內容如下(不需要手敲,文末有代碼下載鏈接):

  1. #include <linux/module.h> 
  2. #include <linux/kernel.h> 
  3. #include <linux/ctype.h> 
  4. #include <linux/device.h> 
  5. #include <linux/cdev.h> 
  6.  
  7. // 新增的頭文件 
  8. #include <asm/siginfo.h> 
  9. #include <linux/pid.h> 
  10. #include <linux/uaccess.h> 
  11. #include <linux/sched/signal.h> 
  12. #include <linux/pid_namespace.h> 
  13.  
  14. // GPIO 硬件相關宏定義 
  15. #define MYGPIO_HW_ENABLE 
  16.  
  17. // 新增部分,使用這個宏控制起來 
  18. #define MY_SIGNAL_ENABLE 
  19.  
  20. // 設備名稱 
  21. #define MYGPIO_NAME         "mygpio" 
  22.  
  23. // 一共有4個GPIO 
  24. #define MYGPIO_NUMBER       4 
  25.  
  26. // 設備類 
  27. static struct class *gpio_class; 
  28.  
  29. // 用來保存設備 
  30. struct cdev gpio_cdev[MYGPIO_NUMBER]; 
  31.  
  32. // 用來保存設備號 
  33. int gpio_major = 0; 
  34. int gpio_minor = 0; 
  35.  
  36. #ifdef MY_SIGNAL_ENABLE 
  37. // 用來保存向誰發送信號,應用程序通過 ioctl 把自己的進程 ID 設置進來。 
  38. static int g_pid = 0; 
  39. #endif 
  40.  
  41. #ifdef MYGPIO_HW_ENABLE 
  42. // 硬件初始化函數,在驅動程序被加載的時候(gpio_driver_init)被調用 
  43. static void gpio_hw_init(int gpio) 
  44.     printk("gpio_hw_init is called: %d. \n", gpio); 
  45.  
  46. // 硬件釋放 
  47. static void gpio_hw_release(int gpio) 
  48.     printk("gpio_hw_release is called: %d. \n", gpio); 
  49.  
  50. // 設置硬件GPIO的狀態,在控制GPIO的時候(gpio_ioctl)被調研 
  51. static void gpio_hw_set(unsigned long gpio_no, unsigned int val) 
  52.     printk("gpio_hw_set is called. gpio_no = %ld, val = %d. \n", gpio_no, val); 
  53. #endif 
  54.  
  55. #ifdef MY_SIGNAL_ENABLE 
  56. // 用來發送信號給應用程序 
  57. static void send_signal(int sig_no) 
  58.     int ret; 
  59.     struct siginfo info; 
  60.     struct task_struct *my_task = NULL
  61.     if (0 == g_pid) 
  62.     { 
  63.         // 說明應用程序沒有設置自己的 PID 
  64.         printk("pid[%d] is not valid! \n", g_pid); 
  65.         return
  66.     } 
  67.  
  68.     printk("send signal %d to pid %d \n", sig_no, g_pid); 
  69.  
  70.     // 構造信號結構體 
  71.     memset(&info, 0, sizeof(struct siginfo)); 
  72.     info.si_signo = sig_no; 
  73.     info.si_errno = 100; 
  74.     info.si_code = 200; 
  75.  
  76.     // 獲取自己的任務信息,使用的是 RCU 鎖 
  77.     rcu_read_lock(); 
  78.     my_task = pid_task(find_vpid(g_pid), PIDTYPE_PID); 
  79.     rcu_read_unlock(); 
  80.  
  81.     if (my_task == NULL
  82.     { 
  83.         printk("get pid_task failed! \n"); 
  84.         return
  85.     } 
  86.  
  87.     // 發送信號 
  88.     ret = send_sig_info(sig_no, &info, my_task); 
  89.     if (ret < 0)  
  90.     { 
  91.            printk("send signal failed! \n"); 
  92.     } 
  93. #endif 
  94.  
  95. // 當應用程序打開設備的時候被調用 
  96. static int gpio_open(struct inode *inode, struct file *file) 
  97.      
  98.     printk("gpio_open is called. \n"); 
  99.     return 0;    
  100.  
  101. #ifdef MY_SIGNAL_ENABLE 
  102. static long gpio_ioctl(struct file* file, unsigned int cmd, unsigned long arg) 
  103.     void __user *pArg; 
  104.     printk("gpio_ioctl is called. cmd = %d \n", cmd); 
  105.     if (100 == cmd) 
  106.     { 
  107.         // 說明應用程序設置進程的 PID  
  108.         pArg = (void *)arg; 
  109.         if (!access_ok(VERIFY_READ, pArg, sizeof(int))) 
  110.         { 
  111.             printk("access failed! \n"); 
  112.             return -EACCES; 
  113.         } 
  114.  
  115.         // 把用戶空間的數據復制到內核空間 
  116.         if (copy_from_user(&g_pid, pArg, sizeof(int))) 
  117.         { 
  118.             printk("copy_from_user failed! \n"); 
  119.             return -EFAULT; 
  120.         } 
  121.  
  122.         printk("save g_pid success: %d \n", g_pid);  
  123.         if (g_pid > 0) 
  124.         { 
  125.             // 發送信號 
  126.             send_signal(SIGUSR1); 
  127.             send_signal(SIGUSR2); 
  128.         } 
  129.     } 
  130.  
  131.     return 0; 
  132. #else 
  133. // 當應用程序控制GPIO的時候被調用 
  134. static long gpio_ioctl(struct file* file, unsigned int val, unsigned long gpio_no) 
  135.     printk("gpio_ioctl is called. \n"); 
  136.      
  137.     if (0 != val && 1 != val) 
  138.     { 
  139.         printk("val is NOT valid! \n"); 
  140.         return 0; 
  141.     } 
  142.  
  143.     if (gpio_no >= MYGPIO_NUMBER) 
  144.     { 
  145.         printk("dev_no is invalid! \n"); 
  146.         return 0; 
  147.     } 
  148.  
  149.     printk("set GPIO: %ld to %d. \n", gpio_no, val); 
  150.  
  151. #ifdef MYGPIO_HW_ENABLE 
  152.     gpio_hw_set(gpio_no, val); 
  153. #endif 
  154.  
  155.     return 0; 
  156. #endif 
  157.  
  158. static const struct file_operations gpio_ops={ 
  159.     .owner = THIS_MODULE, 
  160.     .open  = gpio_open, 
  161.     .unlocked_ioctl = gpio_ioctl 
  162. }; 
  163.  
  164. static int __init gpio_driver_init(void) 
  165.     int i, devno; 
  166.     dev_t num_dev; 
  167.  
  168.     printk("gpio_driver_init is called. \n"); 
  169.  
  170.     // 動態申請設備號(嚴謹點的話,應該檢查函數返回值) 
  171.     alloc_chrdev_region(&num_dev, gpio_minor, MYGPIO_NUMBER, MYGPIO_NAME); 
  172.  
  173.     // 獲取主設備號 
  174.     gpio_major = MAJOR(num_dev); 
  175.     printk("gpio_major = %d. \n", gpio_major); 
  176.  
  177.     // 創建設備類 
  178.     gpio_class = class_create(THIS_MODULE, MYGPIO_NAME); 
  179.  
  180.     // 創建設備節點 
  181.     for (i = 0; i < MYGPIO_NUMBER; ++i) 
  182.     { 
  183.         // 設備號 
  184.         devno = MKDEV(gpio_major, gpio_minor + i); 
  185.          
  186.         // 初始化cdev結構 
  187.         cdev_init(&gpio_cdev[i], &gpio_ops); 
  188.  
  189.         // 注冊字符設備 
  190.         cdev_add(&gpio_cdev[i], devno, 1); 
  191.  
  192.         // 創建設備節點 
  193.         device_create(gpio_class, NULL, devno, NULL, MYGPIO_NAME"%d", i); 
  194.     } 
  195.  
  196. #ifdef MYGPIO_HW_ENABLE 
  197.     for (i = 0; i < MYGPIO_NUMBER; ++i) 
  198.     { 
  199.         // 初始硬件GPIO 
  200.         gpio_hw_init(i); 
  201.     } 
  202. #endif 
  203.  
  204.     return 0; 
  205.  
  206. static void __exit gpio_driver_exit(void) 
  207.     int i; 
  208.     printk("gpio_driver_exit is called. \n"); 
  209.  
  210.     // 刪除設備節點 
  211.     for (i = 0; i < MYGPIO_NUMBER; ++i) 
  212.     { 
  213.         cdev_del(&gpio_cdev[i]); 
  214.         device_destroy(gpio_class, MKDEV(gpio_major, gpio_minor + i)); 
  215.     } 
  216.  
  217.     // 釋放設備類 
  218.     class_destroy(gpio_class); 
  219.  
  220. #ifdef MYGPIO_HW_ENABLE 
  221.     for (i = 0; i < MYGPIO_NUMBER; ++i) 
  222.     { 
  223.         gpio_hw_release(i); 
  224.     } 
  225. #endif 
  226.  
  227.     // 注銷設備號 
  228.     unregister_chrdev_region(MKDEV(gpio_major, gpio_minor), MYGPIO_NUMBER); 
  229.  
  230. MODULE_LICENSE("GPL"); 
  231. module_init(gpio_driver_init); 
  232. module_exit(gpio_driver_exit); 

這里大部分的代碼,在上一篇文章中已經描述的比較清楚了,這里把重點關注放在這兩個函數上:gpio_ioctl 和 send_signal。

(1)函數 gpio_ioctl

當應用程序調用 ioctl() 的時候,驅動程序中的 gpio_ioctl 就會被調用。

這里定義一個簡單的協議:當應用程序調用參數中 cmd 為 100 的時候,就表示用來告訴驅動程序自己的 PID。

驅動程序定義了一個全局變量 g_pid,用來保存應用程序傳入的參數PID。

需要調用函數 copy_from_user(&g_pid, pArg, sizeof(int)),把用戶空間的參數復制到內核空間中;

成功取得PID之后,就調用函數 send_signal 向應用程序發送信號。

這里僅僅是用于演示目的,在實際的項目中,可能會根據接收到硬件觸發之后再發送信號。

(2)函數 send_signal

這個函數主要做了3件事情:

構造一個信號結構體變量:struct siginfo info;

通過應用程序傳入的 PID,獲取任務信息:pid_task(find_vpid(g_pid), PIDTYPE_PID);

發送信號:send_sig_info(sig_no, &info, my_task);

驅動模塊 Makefile

  1. $ touch Makefile 

內容如下:

  1. ifneq ($(KERNELRELEASE),) 
  2.     obj-m := my_driver_signal.o 
  3. else 
  4.     KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
  5.     PWD := $(shell pwd) 
  6. default
  7.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
  8. clean: 
  9.     $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean 
  10. endif 

編譯驅動模塊

  1. $ make 

得到驅動程序: my_driver_signal.ko 。

加載驅動模塊

  1. $ sudo insmod my_driver_signal.ko 

通過 dmesg 指令來查看驅動模塊的打印信息:

因為示例代碼是在上一篇GPIO的基礎上修改的,因此創建的設備節點文件,與上篇文章是一樣的: 

應用程序代碼示例:接收信號

注冊信號處理函數

應用程序仍然放在 ~/tmp/App/ 目錄下。

  1. $ mkdir ~/tmp/App/app_mysignal 
  2. $ cd ~/tmp/App/app_mysignal 
  3. $ touch mysignal.c 

文件內容如下:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <unistd.h> 
  4. #include <assert.h> 
  5. #include <fcntl.h> 
  6. #include <sys/ioctl.h> 
  7. #include <signal.h> 
  8.  
  9. #define MY_GPIO_NUMBER      4 
  10.  
  11. char gpio_name[MY_GPIO_NUMBER][16] = { 
  12.     "/dev/mygpio0"
  13.     "/dev/mygpio1"
  14.     "/dev/mygpio2"
  15.     "/dev/mygpio3" 
  16. }; 
  17.  
  18. // 信號處理函數 
  19. static void signal_handler(int signum, siginfo_t *info, void *context) 
  20.     // 打印接收到的信號值 
  21.     printf("signal_handler: signum = %d \n", signum); 
  22.     printf("signo = %d, code = %d, errno = %d \n"
  23.              info->si_signo, 
  24.              info->si_code,  
  25.              info->si_errno); 
  26.  
  27. int main(int argc, char *argv[]) 
  28.     int fd, count = 0; 
  29.     int pid = getpid(); 
  30.  
  31.     // 打開GPIO 
  32.     if((fd = open("/dev/mygpio0", O_RDWR | O_NDELAY)) < 0){ 
  33.         printf("open dev failed! \n"); 
  34.         return -1; 
  35.     } 
  36.  
  37.     printf("open dev success! \n"); 
  38.      
  39.     // 注冊信號處理函數 
  40.     struct sigaction sa; 
  41.     sigemptyset(&sa.sa_mask); 
  42.     sa.sa_sigaction = &signal_handler; 
  43.     sa.sa_flags = SA_SIGINFO; 
  44.      
  45.     sigaction(SIGUSR1, &sa, NULL); 
  46.     sigaction(SIGUSR2, &sa, NULL); 
  47.  
  48.     // set PID  
  49.     printf("call ioctl. pid = %d \n", pid); 
  50.     ioctl(fd, 100, &pid); 
  51.  
  52.     // 休眠1秒,等待接收信號 
  53.     sleep(1); 
  54.  
  55.     // 關閉設備 
  56.     close(fd); 

可以看到,應用程序主要做了兩件事情:

(1)首先通過函數 sigaction() 向操作系統注冊了信號 SIGUSR1 和 SIGUSR2,它倆的信號處理函數是同一個:signal_handler()。

除了 sigaction 函數,應用程序還可以使用 signal 函數來注冊信號處理函數;

(2)然后通過 ioctl(fd, 100, &pid); 向驅動程序設置自己的 PID。

編譯應用程序:

  1. $ gcc mysignal.c -o mysignal 

執行應用程序:

  1. $ sudo ./mysignal 

根據剛才驅動程序的代碼,當驅動程序接收到設置PID的命令之后,會立刻發送兩個信號:

先來看一下 dmesg 中驅動程序的打印信息:

可以看到:驅動把這兩個信號(10 和 12),發送給了應用程序(PID=6259)。

應用程序的輸出信息如下:

可以看到:應用程序接收到信號 10 和 12,并且正確打印出信號中攜帶的一些信息!

本文轉載自微信公眾號「IOT物聯網小鎮」

 

責任編輯:姜華 來源: IOT物聯網小鎮
相關推薦

2021-11-29 07:55:45

Linux GPIO Linux 系統

2011-01-10 18:21:38

linux編寫程序

2021-12-20 07:51:16

Linux函數應用層

2010-01-07 13:27:22

Linux驅動程序

2009-07-06 18:17:46

JDBC驅動程序

2021-11-12 11:28:01

Linux 內核驅動Linux 系統

2011-01-06 16:29:08

linuxtasklet機制

2018-11-26 08:45:29

Linux驅動程序命令

2013-10-31 16:29:10

Linux內核

2009-12-07 09:39:04

Linux設備驅動硬件通信

2019-10-22 15:40:34

Windows 10驅動程序Windows

2021-11-16 06:55:36

Linux字符設備

2021-11-22 08:14:23

Linux Linux驅動Linux 系統

2023-10-07 09:37:53

2011-04-22 17:29:37

Linux網卡

2009-10-23 10:25:27

驅動程序技巧

2019-03-27 13:20:31

Windows 10更新驅動程序

2018-11-19 10:15:26

Windows 10WiFi驅動程序

2009-08-12 18:20:39

C#事件驅動程序

2017-03-03 08:40:32

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久男人 | 在线观看精品视频网站 | 奇米久久 | www.亚洲一区 | 国产真实精品久久二三区 | 91久久北条麻妃一区二区三区 | 免费在线黄 | 中文字幕视频一区二区 | 在线三级网址 | 人人干视频在线 | 欧美一级艳情片免费观看 | 久www| 久久久久久亚洲精品 | 日日干夜夜草 | 亚洲va国产日韩欧美精品色婷婷 | 免费看a | 国产高清精品一区二区三区 | 久草新在线 | 国产欧美精品一区二区色综合朱莉 | 久久国产精品一区二区三区 | 国产精品性做久久久久久 | 手机av在线 | 国产精品国产a | 国产福利在线视频 | 色婷婷av777| 农村妇女毛片精品久久久 | 国产美女特级嫩嫩嫩bbb片 | 久久在线| 黄色免费网站在线看 | 在线日韩福利 | 国产精品毛片一区二区三区 | 国产资源在线播放 | 欧美伦理一区 | 91精品麻豆日日躁夜夜躁 | 91久久精品一区二区二区 | 午夜视频在线免费观看 | 亚洲一区二区三区四区五区午夜 | 黑人巨大精品 | 亚洲品质自拍视频网站 | 精品一区国产 | 久久久久国产一区二区三区 |