国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Linux > 正文

Linux驅動踐行 中斷處理函數如何 發送信號 給應用層?

2024-08-27 23:31:45
字體:
來源:轉載
供稿:網友
別人的經驗,我們的階梯!
 
大家好,我是道哥,今天我為大伙兒解說的技術知識點是:【中斷程序如何發送信號給應用層】。
 
最近分享的幾篇文章都比較基礎,關于字符類設備的驅動程序,以及中斷處理程序。
 
也許在現代的項目是用不到這樣的技術,但是萬丈高樓平地起。
 
只有明白了這些最基礎的知識點之后,再去看那些進化出來的高級玩意,才會有一步一個腳印的獲得感。
 
如果缺少了這些基礎的環節,很多深層次的東西,學起來就有點空中樓閣的感覺。
 
就好比研究Linux內核,如果一上來就從Linux 4.x/5.x內核版本開始研究,可以看到很多“歷史遺留”代碼。
 
這些代碼就見證著Linux一步一步的發展歷史,甚至有些人還會專門去研究 Linux 0.11 版本的內核源碼,因為很多基本思想都是一樣的。
 
今天這篇文章,主要還是以代碼實例為主,把之前的兩個知識點結合起來:
 
在中斷處理函數中,發送信號給應用層,以此來通知應用層處理響應的中斷業務。
 
 
 
驅動程序
示例代碼全貌
所有的操作都是在 ~/tmp/linux-4.15/drivers 目錄下完成的。
 
首先創建驅動模塊目錄:
 
$ cd ~/tmp/linux-4.15/drivers
$ mkdir my_driver_interrupt_signal
$ touch my_driver_interrupt_signal.c
文件內容如下:
 
#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>
 
// 中斷號
#define IRQ_NUM         1
 
// 定義驅動程序的 ID,在中斷處理函數中用來判斷是否需要處理     
#define IRQ_DRIVER_ID   1234
 
// 設備名稱
#define MYDEV_NAME      "mydev"
 
// 驅動程序數據結構
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
 
// 設備類
static struct class *my_class;
 
// 用來保存設備
struct cdev my_cdev;
 
// 用來保存設備號
int mydev_major = 0;
int mydev_minor = 0;
 
// 用來保存向誰發送信號,應用程序通過 ioctl 把自己的進程 ID 設置進來。
static int g_pid = 0;
 
// 用來發送信號給應用程序
static void send_signal(int sig_no)
{
    int ret;
    struct siginfo info;
    struct task_struct *my_task = NULL;
    if (0 == g_pid)
    {
        // 說明應用程序沒有設置自己的 PID
        printk("pid[%d] is not valid! /n", g_pid);
        return;
    }
 
    printk("send signal %d to pid %d /n", sig_no, g_pid);
 
    // 構造信號結構體
    memset(&info, 0, sizeof(struct siginfo));
    info.si_signo = sig_no;
    info.si_errno = 100;
    info.si_code = 200;
 
    // 獲取自己的任務信息,使用的是 RCU 鎖
    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;
    }
 
    // 發送信號
    ret = send_sig_info(sig_no, &info, my_task);
    if (ret < 0)  
    {
           printk("send signal failed! /n");
    }
}
 
//中斷處理函數
static irqreturn_t myirq_handler(int irq, void * dev)
{
    struct myirq mydev;
    unsigned char key_code;
    mydev = *(struct myirq*)dev;     
     
    // 檢查設備 id,只有當相等的時候才需要處理
    if (IRQ_DRIVER_ID == mydev.devid)
    {
        // 讀取鍵盤掃描碼
        key_code = inb(KBD_DATA_REG);
     
        if (key_code == 0x01)
        {
            printk("EXC key is pressed! /n");
            send_signal(SIGUSR1);
        }
    }    
 
    return IRQ_HANDLED;
}
 
// 驅動模塊初始化函數
static void myirq_init(void)
{
    printk("myirq_init is called. /n");
 
    // 注冊中斷處理函數
    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);
}
 
// 當應用程序打開設備的時候被調用
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)
    {
        // 說明應用程序設置進程的 PID  
        pArg = (void *)arg;
        if (!access_ok(VERIFY_READ, pArg, sizeof(int)))
        {
            printk("access failed! /n");
            return -EACCES;
        }
 
        // 把用戶空間的數據復制到內核空間
        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");
 
    // 注冊中斷處理函數
    if(request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev)!=0)
    {
        printk("register irq[%d] handler failed. /n", IRQ_NUM);
        return -1;
    }
 
    // 動態申請設備號(嚴謹點的話,應該檢查函數返回值)
    alloc_chrdev_region(&num_dev, mydev_minor, 1, MYDEV_NAME);
 
    // 獲取主設備號
    mydev_major = MAJOR(num_dev);
    printk("mydev_major = %d. /n", mydev_major);
 
    // 創建設備類
    my_class = class_create(THIS_MODULE, MYDEV_NAME);
 
    // 創建設備節點
    devno = MKDEV(mydev_major, mydev_minor);
     
    // 初始化cdev結構
    cdev_init(&my_cdev, &mydev_ops);
 
    // 注冊字符設備
    cdev_add(&my_cdev, devno, 1);
 
    // 創建設備節點
    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");
 
    // 刪除設備節點
    cdev_del(&my_cdev);
    device_destroy(my_class, MKDEV(mydev_major, mydev_minor));
 
    // 釋放設備類
    class_destroy(my_class);
 
    // 注銷設備號
    unregister_chrdev_region(MKDEV(mydev_major, mydev_minor), 1);
 
    // 注銷中斷處理函數
    free_irq(IRQ_NUM, &mydev);
}
 
MODULE_LICENSE("GPL");
module_init(mydev_driver_init);
module_exit(mydev_driver_exit);
以上代碼主要做了兩件事情:
 
注冊中斷號 1 的處理函數:myirq_handler();
創建設備節點 /dev/mydev;
這里的中斷號1,是鍵盤中斷。
 
因為它是共享的中斷,因此當鍵盤被按下的時候,操作系統就會依次調用所有的中斷處理函數,當然就包括我們的驅動程序所注冊的這個函數。
 
中斷處理部分相關的幾處關鍵代碼如下:
 
//中斷處理函數
static irqreturn_t myirq_handler(int irq, void * dev)
{
    ...
}
 
// 驅動模塊初始化函數
static void myirq_init(void)
{
    ...
    request_irq(IRQ_NUM, myirq_handler, IRQF_SHARED, MYDEV_NAME, &mydev);
    ...
}
在中斷處理函數中,目標是發送信號 SIGUSR1 到應用層,因此驅動程序需要知道應用程序的進程號(PID)。
 
根據之前的文章Linux驅動實踐:驅動程序如何發送【信號】給應用程序?,應用程序必須主動把自己的 PID 告訴驅動模塊才可以。這可以通過 write 或者ioctl函數來實現,
 
驅動程序用來接收 PID 的相關代碼是:
 
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));
    }
}
知道了應用程序的 PID,驅動程序就可以在中斷發生的時候(按下鍵盤ESC鍵),發送信號出去了:
 
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 文件
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
編譯、測試
首先查看一下加載驅動模塊之前,1號中斷的所有驅動程序:
 
 
 
再看一下設備號:
 
$ cat /proc/devices
 
 
因為驅動注冊在創建設備節點的時候,是動態請求系統分配的。
 
根據之前的幾篇文章可以知道,系統一般會分配244這個主設備號給我們,此刻還不存在這個設備號。
 
編譯、加載驅動模塊:
 
$ make
$ sudo insmod my_driver_interrupt_signal.ko
首先看一下 dmesg 的輸出信息:
 
 
 
然后看一下中斷驅動程序:
 
 
 
可以看到我們的驅動程序( mydev )已經登記在1號中斷的最右面。
 
最后看一下設備節點情況:
 
 
 
驅動模塊已經準備妥當,下面就是應用程序了。
 
應用程序
應用程序的主要功能就是兩部分:
 
通過 ioctl 函數把自己的 PID 告訴驅動程序;
 
注冊信號 SIGUSR1 的處理函數;
 
示例代碼全貌
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
 
編譯、測試
 
新開一個中斷窗口,編譯、執行應用程序:
 
$ gcc my_interrupt_singal.c -o my_interrupt_singal
$ sudo ./my_interrupt_singal
open dev success!  
call ioctl. pid = 12907
 
// 這里進入 while 循環
由于應用程序調用了 open 和 ioctl 這兩個函數,因此,驅動程序中兩個對應的函數就會被執行。
 
這可以通過 dmesg 命令的輸出信息看出來:
 
 
 
這個時候,按下鍵盤上的 ESC 鍵,此時驅動程序中打印如下信息:
 
 
 
說明:驅動程序捕獲到了鍵盤上的 ESC 鍵,并且發送信號給應用程序了。

(編輯:武林網)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 沅江市| 涞水县| 陆河县| 和静县| 射洪县| 绥化市| 盐池县| 洛阳市| 全州县| 兴安县| 廉江市| 大名县| 中宁县| 内黄县| 黑河市| 桐梓县| 大丰市| 凤城市| 吉木乃县| 扶余县| 岳普湖县| 安陆市| 白河县| 建平县| 边坝县| 乐清市| 沾化县| 永胜县| 富源县| 宿州市| 富阳市| 丹江口市| 元江| 石嘴山市| 乐至县| 奉新县| 灌南县| 商都县| 通州市| 聊城市| 峡江县|