linux核心有中斷函數。在Linux核心中要想使用某個中斷是需要申請的,而request_irq()函數用來申請中斷,中斷使用完成以後就要透過free_irq()函式釋放掉對應的中斷;還有enable_irq()和disable_irq( ),它們用於使能和禁止指定的中斷。
本教學操作環境:linux7.3系統、Dell G3電腦。
1.Linux中斷
1.1 Linux中斷API函數
request_irq函數
#在Linux 核心中要想使用某個中斷是需要申請的,request_irq 函數用於申請中斷,request_irq函數可能會導致睡眠,因此不能在中斷上下文或其他禁止睡眠的程式碼片段中使用request_irq 函數。 request_irq 函數會啟動(使能)中斷,所以不需要我們手動去啟用中斷,request_irq 函數原型如下:
irq:要申請中斷的中斷號碼。
handler:中斷處理函數,當中斷發生以後就會執行此中斷處理函數。
flags:中斷標誌,可以在檔案include/linux/interrupt.h 裡面查看所有的中斷標誌
name:中斷名字,設定以後可以在/proc/interrupts 檔案中看到對應的中斷名字。
dev:如果將flags 設定為IRQF_SHARED 的話,dev 用來區分不同的中斷,一般情況下將dev 設定為裝置結構體,dev 會傳遞給中斷處理函數irq_handler_t 的第二個參數。
回傳值:0 中斷申請成功,其他負值 中斷申請失敗,如果回傳-EBUSY 的話表示中斷已經被申請了。
free_irq
使用中斷的時候需要透過 request_irq 函數申請,使用完成以後就要透過 free_irq 函數釋放對應的中斷。如果中斷不是共享的,那麼 free_irq 會刪除中斷處理函數並且禁止中斷。 free_irq函數原型如下:
函數參數與傳回值意義如下:
irq:要釋放的中斷。
dev:如果中斷設定為共享(IRQF_SHARED)的話,此參數用來區分特定的中斷。共享中斷只有在釋放最後中斷處理函數的時候才會被禁止掉。
傳回值:無。
中斷處理函數
使用request_irq 函數申請中斷的時候需要設定中斷處理函數,中斷處理函數格式如下所示:
中斷啟用與禁止函數
常用的中斷使用和禁止函數如下:
enable_irq 和 disable_irq 用於啟用和禁止指定的中斷,irq 就是要禁止的中斷號。 disable_irq函數要等到目前正在執行的中斷處理函數執行完才返回,因此使用者需要保證不會產生新的中斷,並且確保所有已經開始執行的中斷處理程序已經全部退出。在這種情況下,可以使用另一個中斷禁止函數:
disable_irq_nosync 函數呼叫以後立即傳回,不會等待目前中斷處理程序執行完畢。以上三個函數都是使能或禁止某一個中斷,有時候我們需要關閉目前處理器的整個中斷系統,也就是在學習STM32 的時候常說的關閉全域中斷,這個時候可以使用以下兩個函數:
local_irq_enable 用於啟用目前處理器中斷系統,local_irq_disable 用於禁止目前處理器中斷系統。假如 A 任務呼叫 local_irq_disable 關閉全域中斷 10S,當關閉了 2S 的時候 B 任務開始運行,B 任務也呼叫 local_irq_disable 關閉全域中斷 3S,3 秒以後 B 任務呼叫 local_irq_enable 函數將全域中斷開啟了。此時才過去2 3=5 秒的時間,然後全局中斷就被打開了,此時A 任務要關閉10S 全局中斷的願望就破滅了,然後A 任務就“生氣了”,結果很嚴重,可能係統都要被A 任務崩潰。為了解決這個問題,B 任務不能直接簡單粗暴的透過local_irq_enable 函數來開啟全域中斷,而是將中斷狀態恢復到先前的狀態,要考慮到別的任務的感受,此時就要用到下面兩個函數:
1.2 上半部與下半部
在有些資料中也將上半部和下半部稱為頂半部和底半部,都是一個意思。我們在使用request_irq 申請中斷的時候註冊的中斷服務函數屬於中斷處理的上半部,只要中斷觸發,那麼中斷處理函數就會執行。我們都知道中斷處理函數一定要快點執行完畢,越短越好,但是現實往往是殘酷的,有些中斷處理過程就是比較費時間,我們必須要對其進行處理,縮小中斷處理函數的執行時間。例如電容觸控螢幕透過中斷通知 SOC 有觸控事件發生,SOC 回應中斷,然後透過 IIC 介面讀取觸控座標值並將其回報給系統。但我們都知道 IIC 的速度最高也只有400Kbit/S,所以在中斷中透過 IIC 讀取資料就會浪費時間。我們可以將透過 IIC 讀取觸控資料的操作暫後執行,中斷處理函數僅相應中斷,然後清除中斷標誌位元即可。這時候中斷處理過程就分為了兩個部分:
上半部:上半部就是中斷處理函數,那些處理過程比較快,不會佔用很長時間的處理就可以放在上半部完成。
下半部:如果中斷處理過程比較耗時,那麼就將這些比較耗時的程式碼提出來,交給下半部去執行,這樣中斷處理函數就會快進快出。
因此,Linux 核心將中斷分為上半部和下半部的主要目的就是實現中斷處理函數的快進快出,那些對時間敏感、執行速度快的操作可以放到中斷處理函數中,也就是上半部。剩下的所有工作都可以放到下半部去執行,例如在上半部將資料拷貝到記憶體中,關於資料的具體處理就可以放到下半部去執行。至於哪些程式碼屬於上半部,哪些程式碼屬於下半部並沒有明確的規定,一切根據實際使用情況去判斷,這個就很考驗驅動編寫人員的功底了。這裡有一些可以藉鏡的參考點:
①、如果要處理的內容不希望被其他中斷打斷,那麼可以放到上半部。
②、如果要處理的任務對時間敏感,可以放到上半部。
③、如果要處理的任務與硬體有關,可以放到上半部
④、除了上述三點以外的其他任務,優先考慮放到下半部。
下半部機制:
軟體中斷
一開始 Linux 核心提供了「bottom half」機制來實現下半部,簡稱「BH」。後面引入了軟中斷和 tasklet 來取代「BH」機制,完全可以使用軟中斷和 tasklet 來取代 BH,從 2.5 版本的 Linux核心開始 BH 已經被拋棄了。 Linux 核心使用架構softirq_action 表示軟體中斷, softirq_action架構定義在檔案include/linux/interrupt.h 中,內容如下:
在kernel/softirq.c 檔案中一共定義了10 個軟中斷,如下所示:
NR_SOFTIRQS 是枚舉類型,定義在檔案include/linux/interrupt.h 中,定義如下:
#可以看出,總共有10 個軟體中斷,因此NR_SOFTIRQS 為10,因此陣列softirq_vec 有10 個元素。 softirq_action 結構體中的action 成員變數就是軟中斷的服務函數,數組softirq_vec 是個全域數組,因此所有的CPU(對於SMP 系統而言)都可以存取到,每個CPU 都有自己的觸發和控制機制,並且只執行自己所觸發的軟中斷。但各個 CPU 所執行的軟體中斷服務函數確是相同的,都是在陣列 softirq_vec 中定義的 action 函數。若要使用軟體中斷,必須先使用open_softirq 函數註冊對應的軟體中斷處理函數,open_softirq 函數原型如下:
nr:要開啟的軟體中斷,在範例程式碼51.1 .2.3 中選擇一個。
action:軟中斷對應的處理函數。
傳回值:沒有傳回值。
註冊好軟中斷以後需要透過 raise_softirq 函數觸發,raise_softirq 函數原型如下:
軟體中斷必須在編譯的時候靜態註冊! Linux 核心使用softirq_init 函式初始化軟體中斷,softirq_init 函式定義在kernel/softirq.c 檔案裡面,函式內容如下:
tasklet
# tasklet 是利用軟體中斷實現的另一個下半部機制,在軟體中斷和tasklet 之間,建議大家使用tasklet。 Linux 核心使用結構體
第 489 行的 func 函數就是 tasklet 要執行的處理函數,使用者定義函數內容,相當於中斷處理函數。如果要使用tasklet,必須先定義一個tasklet,然後使用tasklet_init 函數初始化tasklet,taskled_init 函數原型如下:##函數參數和傳回值意義如下:
t:要初始化的tasklet
func:tasklet 的處理函數。
data:要傳遞給 func 函數的參數
傳回值:沒有傳回值。 也可以使用巨集DECLARE_TASKLET來一次完成tasklet的定義和初始化DECLARE_TASKLET 定義在include/linux/interrupt.h 檔案中,定義如下:其中name 為要定義的tasklet 名字,這個名字就是一個tasklet_struct 類型的時候變量,func就是tasklet 的處理函數,data 是傳遞給func 函數的參數。
在上半部,也就是中斷處理函數中呼叫tasklet_schedule 函數就能使tasklet 在適當的時間運行,tasklet_schedule 函數原型如下:關於tasklet 的參考使用範例如下所示:
工作佇列
工作佇列是另一種下半部執行方式,工作佇列在進程上下文執行,工作佇列將要推後的工作交給一個核心執行緒去執行,因為工作佇列工作在進程上下文,因此工作隊列允許睡眠或重新調度。因此如果你要推後的工作可以睡那麼就可以選擇工作隊列,否則的話就只能選擇軟中斷或 tasklet。
Linux 核心使用work_struct 結構體表示一個工作,內容如下(省略掉條件編譯):
這些工作組織成工作佇列,工作佇列使用workqueue_struct 結構體表示,內容如下(省略掉條件編譯):
Linux 核心使用工作者執行緒(worker thread)來處理工作佇列中的各個工作,Linux 核心使用worker 結構體表示工作者線程,worker 結構體內容如下:
從範例程式碼51.1.2.10 可以看出,每個worker 都有一個工作佇列,工作者執行緒處理自己工作佇列中的所有工作。在實際的驅動開發中,我們只需要定義工作(work_struct)即可,關於工作佇列和工作者執行緒我們基本上不用去管。簡單建立工作很簡單,直接定義一個 work_struct 結構體變數即可,然後使用 INIT_WORK 巨集來初始化工作,INIT_WORK 巨集定義如下:
1.3 設備樹中斷訊息節點
如果使用設備樹的話就需要在設備樹中設定好中斷屬性訊息,Linux 核心透過讀取設備樹中的中斷屬性訊息來配置中斷。對於中斷控制器而言,裝置樹綁定資訊參考文件Documentation/devicetree/bindings/arm/gic.txt。開啟imx6ull.dtsi 文件,其中的intc 節點就是I.MX6ULL 的中斷控制器節點,節點內容如下:
第2 行,compatible 屬性值為「arm,cortex-a7- gic」在Linux 核心原始碼中搜尋「arm,cortex-a7-gic」即可找到GIC 中斷控制器驅動檔。
第 3 行,#interrupt-cells 和#address-cells、#size-cells 一樣。表示此中斷控制器下設備的 cells大小,對於設備而言,會使用 interrupts 屬性描述中斷訊息,#interrupt-cells 描述了 interrupts 屬性的 cells 大小,也就是一條訊息有幾個 cells。每個cells 都是32 位元整形值,對於ARM 處理的GIC 來說,一共有3 個cells,這三個cells 的含義如下:
第一個cells:中斷類型,0 表示SPI 中斷,1 表示PPI 中斷。
第二個 cells:中斷號,對於 SPI 中斷來說中斷號的範圍為 0~987,對於 PPI 中斷來說中斷號的範圍為 0~15。
第三個cells:標誌,bit[3:0]表示中斷觸發類型,為1 的時候表示上升沿觸發,為2 的時候表示下降沿觸發,為4 的時候表示高電平觸發,為8 的時候表示低電平觸發。 bit[15:8]為 PPI 中斷的 CPU 遮罩。
第 4 行,interrupt-controller 節點為空,表示目前節點為中斷控制器。
對於gpio 來說,gpio 節點也可以作為中斷控制器,例如imx6ull.dtsi 檔案中的gpio5 節點內容如下所示:
第4 行,interrupts 描述中斷來源訊息,對於gpio5 來說一共有兩個訊息,中斷類型都是SPI,觸發電平都是IRQ_TYPE_LEVEL_HIGH。不同之處在於中斷來源,一個是74,一個是75,開啟可以開啟《IMX6ULL 參考手冊》的「Chapter 3 Interrupts and DMA Events」章節,找到表3-1,有如圖50.1.3.1 所示的內容:
從圖50.1.3.1 可以看出,GPIO5 一共用了2 個中斷號,一個是74,一個是75。其中 74 對 應 GPIO5_IO00~GPIO5_IO15 這低 16 個 IO,75 對應GPIO5_IO16~GPIOI5_IO31 這高 16 位元 IO。第 8 行,interrupt-controller 顯示了 gpio5 節點也是個中斷控制器,用於控制 gpio5 所有 IO
的中斷。
第 9 行,將#interrupt-cells 修改為 2。
開啟imx6ull-alientek-emmc.dts 文件,找到如下所示內容:
1.4 取得中斷號
寫驅動程式的時候需要用到中斷號,我們用到中斷號,中斷資訊已經寫到了裝置樹裡面,因此可以透過irq_of_parse_and_map 函數從interupts 屬性中提取到對應的裝置號,函數原型如下:
函數參數與傳回值意義如下:
dev:裝置節點。
index:索引號,interrupts 屬性可能包含多條中斷訊息,透過 index 指定要取得的資訊。
傳回值:中斷號。
如果使用GPIO 的話,可以使用gpio_to_irq 函數來取得gpio 對應的中斷號,函數原型如下:
##2.驅動程式碼#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#define IMX6UIRQ_CNT 1 /* 设备号个数 */
#define IMX6UIRQ_NAME "irqDev" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0 按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 可能会有好多按键,通过结构体数组来描述按键 */
/* 中断 IO 描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* irq设备结构体 */
struct imx6uirq_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 注设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
atomic_t keyvalue; /* 有效的按键键值 */
atomic_t releasekey; /* 标记是否完成一次完成的按键*/
struct timer_list timer; /* 定义一个定时器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
};
struct imx6uirq_dev irqDev; /* 定义LED结构体 */
/* @description : 中断服务函数,开启定时器,延时 10ms,
* 定时器用于按键消抖。
* 两个参数是中断处理函数的必须写法
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
/* 采用定时器削抖,如果再定时器时间内还是这个值,说明是真的按下了,在定时器中断中处理 */
/* 这里设置为0是简易处理,因为只有一个按键 */
/* 有其他按键要再建一个中断处理函数,并把curkeynum改成相应的按键值 */
/* 注意不能所有按键用一个中断函数,第一是一起按的时候会出错 */
/* 第二,无法用curkeynum判断使用的是第几个按键 */
dev->curkeynum = 0;
/* 传递给定时器的参数,注意要强转,在中断处理函数里面再转回来 */
dev->timer.data = (volatile long)dev_id;
/* mod_timer会启动定时器,第二个参数是要修改的超时时间 */
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param – arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
/* 因为只有一个按键,这里是0 */
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
if(value == 0){ /* 按下按键 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按键松开 */
/* 这种情况是按下再松开的松开,使用keyValue加上releaseKey */
/* 没按下的话, releasekey一直为0*/
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
}
}
/*
* @description : 按键 IO 初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
/* 1.获取key节点 */
irqDev.nd = of_find_node_by_path("/key");
if (irqDev.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 对每个按键都提取 GPIO */
for (i = 0; i < KEY_NUM; i++) {
irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i);
if (irqDev.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化 key 所使用的 IO,并且设置成中断模式 */
for (i = 0; i < KEY_NUM; i++) {
/* 先对每一个IO命名 */
/* 先对命名清0 */
memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name));
/* 给IO命名 */
sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i);
/* 请求GPIO */
gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name);
/* 设置GPIO为输入 */
gpio_direction_input(irqDev.irqkeydesc[i].gpio);
/* 获取中断号,以下为两个方法,都可以获取到 */
/* 从interrupts属性里面获取 */
/* 注意i是根据设备树里面设置了多少个就是多少个,都会获取到 */
/* 下面的方法是通用的获取中断号的函数 */
irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i);
#if 0
/* 此方法是gpio获取中断号的方法 */
irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio);
#endif
printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio,
irqDev.irqkeydesc[i].irqnum);
}
/* 2. 按键中断初始化 */
/* 设置中断处理函数和按键初始值 */
/* 因为只有一个key0.,所以这里也没用循环 */
irqDev.irqkeydesc[0].handler = key0_handler;
irqDev.irqkeydesc[0].value = KEY0VALUE;
/* 申请中断 */
for (i = 0; i < KEY_NUM; i++) {
/* request_irq参数
* 中断号,中断函数,中断触发类型,中断名字,传递给中断处理函数的参数(第二个),这里传的结构体
* */
ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
irqDev.irqkeydesc[i].name, &irqDev);
if(ret < 0){
printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 3. 创建定时器 */
init_timer(&irqDev.timer);
irqDev.timer.function = timer_function;
/* 注意下面不能让定时器运行,因为要按下按键之后再运行 */
/* 启动定时器通过mod_timer启动,通常在初始化阶段的定时器用的是add_timer */
return 0;
}
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &irqDev;
return 0;
}
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
//struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
return 0;
}
/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param – buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0; /* 按键值 */
unsigned char releasekey = 0; /* 标记是否一次完成 */
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80; /* 因为中断中或了一个0x80,这里面去掉0x80 */
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
} else { /* 没有按下 */
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
/* 字符设备操作集 */
static const struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.release = imx6uirq_release,
.read = imx6uirq_read
};
/* 模块入口函数 */
static int __init imx6uirq_init(void)
{
/* 定义一些所需变量 */
int ret = 0;
/* 1. 注册字符设备驱动 */
irqDev.major = 0;
if(irqDev.major) {
irqDev.devid = MKDEV(irqDev.major, 0);
ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
} else {
alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
irqDev.major = MAJOR(irqDev.devid);
irqDev.minor = MINOR(irqDev.devid);
}
if(ret < 0){
goto fail_devid;
}
printk("Make devid success! \r\n");
printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor);
/* 2. 初始化cdev */
irqDev.cdev.owner = THIS_MODULE;
cdev_init(&irqDev.cdev, &imx6uirq_fops);
ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT);
if (ret < 0){
goto fail_cdev;
} else {
printk("Cdev add sucess! \r\n");
}
/* 3. 自动创建设备节点 */
irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME );
if(IS_ERR(irqDev.class)) {
ret = PTR_ERR(irqDev.class);
goto fail_class;
} else {
printk("Class create sucess! \r\n");
}
irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME );
if(IS_ERR(irqDev.device)) {
ret = PTR_ERR(irqDev.device);
goto fail_device;
} else {
printk("Device create sucess! \r\n");
}
/* 4.初始化按键 */
atomic_set(&irqDev.keyvalue, INVAKEY);
atomic_set(&irqDev.releasekey, 0);
keyio_init();
printk("irqDev init! \r\n");
return 0;
/* 错误处理 */
fail_device:
class_destroy(irqDev.class);
fail_class:
cdev_del(&irqDev.cdev);
fail_cdev:
unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
fail_devid:
return ret;
}
/* 模块出口函数 */
static void __exit imx6uirq_exit(void)
{
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&irqDev.timer);
/* 释放中断 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev);
}
/* 1. 释放设备号 */
cdev_del(&irqDev.cdev);
/* 2. 注销设备号 */
unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
/* 3. 摧毁设备 */
device_destroy(irqDev.class, irqDev.devid);
/* 4.摧毁类 */
class_destroy(irqDev.class);
printk("irqDev exit! \r\n");
}
/* 模块入口和出口注册 */
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");
3.應用程式碼#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
/*
* argc: 应用程序参数个数
* argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
* .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
* .chrdevbaseApp /dev/led 0 关灯
* .chrdevbaseApp /dev/led 1 开灯
* */
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
int fd, ret;
char *filename;
unsigned char data;
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed! \r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 数据读取错误或者无效 */
} else { /* 数据读取正确 */
if (data) /* 读取到数据 */
printf("key value = %#X\r\n", data);
}
}
close(fd);
return 0;
}
4.使用tasklet處理中斷下半部#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"
/*
* argc: 应用程序参数个数
* argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
* .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
* .chrdevbaseApp /dev/led 0 关灯
* .chrdevbaseApp /dev/led 1 开灯
* */
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Error Usage!\r\n");
return -1;
}
int fd, ret;
char *filename;
unsigned char data;
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("file %s open failed! \r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 数据读取错误或者无效 */
} else { /* 数据读取正确 */
if (data) /* 读取到数据 */
printf("key value = %#X\r\n", data);
}
}
close(fd);
return 0;
}
5.工作佇列處理下半部
開發方式同tasklet
注意work是可以推導出裝置dev結構體的,所以一般將work放在dev結構體裡
Linux影片教學》
以上是linux核心有中斷函數嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!

使用Linux維護模式的時機和原因:1)系統啟動問題時,2)進行重大系統更新或升級時,3)執行文件系統維護時。維護模式提供安全、控制的環境,確保操作的安全性和效率,減少對用戶的影響,並增強系統的安全性。

Linux中不可或缺的命令包括:1.ls:列出目錄內容;2.cd:改變工作目錄;3.mkdir:創建新目錄;4.rm:刪除文件或目錄;5.cp:複製文件或目錄;6.mv:移動或重命名文件或目錄。這些命令通過與內核交互執行操作,幫助用戶高效管理文件和系統。

在Linux中,文件和目錄管理使用ls、cd、mkdir、rm、cp、mv命令,權限管理使用chmod、chown、chgrp命令。 1.文件和目錄管理命令如ls-l列出詳細信息,mkdir-p遞歸創建目錄。 2.權限管理命令如chmod755file設置文件權限,chownuserfile改變文件所有者,chgrpgroupfile改變文件所屬組。這些命令基於文件系統結構和用戶、組系統,通過系統調用和元數據實現操作和控制。

MaintenancemodeInuxisAspecialBootenvironmentforforcalsystemmaintenancetasks.itallowsadMinistratorStoperFormTaskSlikerSettingPassingPassingPasswords,RepairingFilesystems,andRecoveringFrombootFailuresFailuresFailuresInamInimAlenimalenimalenrenmentrent.ToEnterMainterMainterMaintErmaintErmaintEncemememodeBoode,Interlecttheboo

Linux的核心組件包括內核、文件系統、Shell、用戶空間與內核空間、設備驅動程序以及性能優化和最佳實踐。 1)內核是系統的核心,管理硬件、內存和進程。 2)文件系統組織數據,支持多種類型如ext4、Btrfs和XFS。 3)Shell是用戶與系統交互的命令中心,支持腳本編寫。 4)用戶空間與內核空間分離,確保系統穩定性。 5)設備驅動程序連接硬件與操作系統。 6)性能優化包括調整系統配置和遵循最佳實踐。

Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

Linux的維護模式可以通過GRUB菜單進入,具體步驟為:1)在GRUB菜單中選擇內核並按'e'編輯,2)在'linux'行末添加'single'或'1',3)按Ctrl X啟動。維護模式提供了一個安全環境,適用於系統修復、重置密碼和系統升級等任務。

進入Linux恢復模式的步驟是:1.重啟系統並按特定鍵進入GRUB菜單;2.選擇帶有(recoverymode)的選項;3.在恢復模式菜單中選擇操作,如fsck或root。恢復模式允許你以單用戶模式啟動系統,進行文件系統檢查和修復、編輯配置文件等操作,幫助解決系統問題。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

WebStorm Mac版
好用的JavaScript開發工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

記事本++7.3.1
好用且免費的程式碼編輯器