中斷控制與實現

一、中斷基本概念

1、中斷

????????中斷是一種異步事件,用于通知處理器某個事件已經發生,需要處理器立即處理。由于I/O操作的不確定因素以及處理器和I/O設備之間的速度不匹配I/O設備可以通過某種硬件信號異步喚醒對應的處理器的響應,這些硬件信號叫做中斷Linux系統中斷使用的是中斷編號,這個中斷編號是由引腳編號獲取的,引腳編號就是芯片對應的GPIO口。

2、中斷線

????????在允許中斷的計算機系統中,通常會有一個中斷控制器---相當于NVIC,可以控制多路獨立的中斷,掛載多條中斷線,有些中斷線是專用的,而有些中斷線可以被多個外設共享(共享中斷)。Linux系統中斷,靈活性更高---因為中斷編號和中斷服務函數是利用API函數進行綁定的。中斷服務函數是自定義的,還可以帶參數(好處就在于可以利用參數判斷,當前是哪個引腳動作了)

3、IRQ--interrupt request

????????Linux內核為每個中斷線分配一個唯一的中斷編號(IRQ,也稱為中斷請求號。不同的計算機系統分配方式不同的。

4、ISR--interrupt service routine

????????系統響應外設的中斷處理是過外設所在的中斷線上綁定的中斷服務例程/中斷服務函數ISR)實現的

注意:一個中斷線可以綁定一個或多個中斷服務例程,多個中斷線可以綁定同一個中斷服務例程。

總結:

????????1:多對一、一對多????

????????2:一個外設只能對應一個中斷服務例程。

5、中斷工作機制

中斷處理過程:

????????當外設發出中斷請求時,會通過所在的中斷線傳遞給系統,處理器會停止當前的工作,保存斷點,然后跳到該中斷線所綁定的中斷服務函數(ISR),執行相應的中斷服務函數。中斷服務處理完畢后,處理器再返回到斷點繼續執行原來的工作。

6、共享中斷和重用中斷服務函數的區別

(1)掛載不同中斷線上的不同外設,通常使用不同的中斷服務例程,無需通過設備參數進行區分。如果這些外設中斷服務處理的框架是相同,則可以使用同一個中斷服務例程,通過設備參數來區分不同的外設。這個情況不是共享中斷,而是重用中斷服務例程。

(2)掛載同一個中斷線上的不同外設,使用同一個中斷服務例程,必須參數來區分不同的外設此時稱為共享中斷

7、中斷頂半部和中斷底半部

中斷不能占用處理器太久的時間,否則會影響系統的性能,但是有些中斷事務比較耗時,怎么辦?

????????Linux內核將中斷分為頂半部和低半部機制中斷服務例程屬于頂半部,在中斷服務例程結束之前可以啟動中斷底半部機制,將耗時的事務交給中斷低半部處理。

????????頂半部通常會立即執行,時效性更高。而底半部是盡快執行,不能保證立即執行,而是內核擇機調度,有機會就執行。常用的底半部機制包括軟中斷softirq、tasklet和工作隊列。softirq和tasklet運行于中斷上下文,不能睡眠工作隊列workqueue運行于進程上下文,可以睡眠。核定時器timer作為定時機制,可以用于中斷底半部,實現精準的延時功能。

總結:

1:linux系統中斷控制不允許中斷嵌套
2:中斷控制分為中斷頂半部和中斷底半部

3:運行于中斷上下文??(不能睡眠):中斷頂半部??、??定時器回調????、軟中斷SoftIRQ、tasklet??
???????運行于進程上下文(能睡眠)??:???Workqueue

二、中斷頂半部相關函數

1、中斷的注冊和注銷

request_irq

函數功能:

????????注冊中斷,綁定對應的中斷服務函數,并指明中斷的中斷編號,中斷屬性,設備參數

頭文件:

????????#include <linux/interrupt.h>

函數原型:

????????int request_irq(unsignedint irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);

函數參數:

????????unsignedint irq:指定的中斷編號

????????irq_handler_t handler:中斷服務函數。這是一個指向函數的指針,當指定的中斷發生時,該函數將被調用。中斷處理函數必須符合特定的原型,通常接受兩個參數:表示中斷號的中斷號和表示設備標識的 void * 指針。--->原型:typedef irqreturn_t (*irq_handler_t)(int, void *);返回值為IRQ_HANDLED;

????????unsigned long flags:中斷屬性,指的是觸發條件-->上升沿或則下降沿

????????const char *name:中斷的名稱。這是一個字符串,用于標識請求中斷的設備。作用:內核會根據這個名字創建/proc/irq/中斷編號/name/ 目錄spurious這個文件會記錄中斷發生的次數

????????void *dev:這個參數是傳遞給中斷服務函數。對共享中斷來說,這個參數一定有要,當注銷共享中斷中的其中一個時,用這個指定來標識要注銷哪一個。對于有惟一入口的中斷,可以傳遞NULL,但是一般來說都會傳遞一個有意義指針,在中斷程序中使用,以方便編程

函數返回值:

????????成功?返回? 0

????????失敗?返回 <0(指定的中斷號已經被另一個設備占用,或者沒有足夠的資源來分配中斷處理程序)

Irq如何獲取?

如果是GPIO外設中斷,可以查詢GPIO引腳編號即可因為中斷編號是由GPIO引腳編號獲取的,引腳編號就是芯片對應的GPIO口。

gpio_to_irq

函數功能:

????????獲取指定的GPIO引腳編號的中斷編號

頭文件

????????#include <linux/gpio.h>

函數原型

????????int gpio_to_irq(unsigned gpio)

函數參數:

????????unsigned gpio:GPIO口引腳編號 ?

函數返回值

????????成功 >=0 ?表示對應引腳的中斷編號 ?失敗 <0

free_irq

函數功能:

????????注銷指定中斷線的指定的設備,取消該設備觸發中斷

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void free_irq(unsigned int irq,void *dev_id);

函數參數:

????????unsigned int?irq:代表指定中斷線上的中斷編號 ?

????????void *dev_id:掛在該中斷線上的設備的標識信息,必須與request_irq保持一致

函數返回值

2、中斷的使能和失能

enable_irq

函數功能:

????????使能處理器上的中斷線

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void ?enable_irq(unsigned int irq)

函數參數:

????????unsigned int?irq:代表指定中斷線上的中斷編號 ?

函數返回值

disable_irq

函數功能:

????????禁止指定的中斷線

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void disable_irq(unsigned int irq)

函數參數:

????????unsigned int?irq:代表指定中斷線上的中斷編號 ?

函數返回值

disable_irq_nosync

函數功能:

????????禁用一次中斷線,不會等待該中斷線上的中斷服務例程,可以用于中斷上下文

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void disable_irq_nosync(unsigned int irq)

函數參數:

????????unsigned int?irq:代表指定中斷線上的中斷編號 ?

函數返回值

?3、示例

內核源碼里面有對應的中斷觸發方式:

#define??IRQF_TRIGGER_RISING???0x00000001//上升沿

#define??IRQF_TRIGGER_FALLING??0x00000002//下降沿

利用按鍵(原理圖),轉化引腳編號,定義一個結構體對象,觸發中斷動作

代碼編寫思路:

應用層->app-打開對應的文件設備節點,read讀取按鍵的狀態(低電平-按鍵按下),緊接著判斷獲取到的電平狀態并且打印顯示按鍵按下或者按下抬起

驅動層->雜項字符設備驅動模型+硬件操作--按鍵(GPIO0_A5-中斷注冊)--中斷服務函數(獲取按鍵引腳的電平狀態)---xxx_read中將數據上傳到應用層cope_to_user()

//應用層編程步驟

按下按鍵,將按鍵的狀態顯打印出來

//驅動程序編程步驟

第一步:入口函數的實現

  1. 檢查給定的 GPIO編號是否有效
  2. 注冊指定的GPIO引腳資源
  3. 將指定的GPIO引腳設置為輸模式
  4. 獲取指定的GPIO的中斷編號
  5. 注冊中斷,綁定對應的中斷服務函數
  6. 注冊雜項設備驅動模型

第二步:中斷服務函數的具體實現

  1. 獲取按鍵的電平狀態-gpio_get_value()
  2. 將獲取到的電平值,保存到k_buf里面

第三步:read函數的具體實現

????????1.將獲取到的數據上傳到應用層->cope_to_user()

第四步:出口函數的實現

????????1.釋放指定的GPIO

????????2.釋放指定中斷線

????????3.注銷雜項設備驅動模型

//定義一個key引腳相關的結構體

struct key_init{

????int num; ???????//序號

????int gpio; ??????//引腳編號

? ? char name[10]; ?//引腳名稱

? ? int irq; ???????//中斷號

? ? int flag; //觸發方式

};

//聲明變量

static struct key_init keys[]={

???????{0,5,"key0",0,IRQF_TRIGGER_RISING?| IRQF_TRIGGER_FALLING},

???????{},

};

應用層代碼:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//調用一下open函數fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//獲取驅動層硬件的電平狀態read(fd,r_buf,1);//避免重復顯示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//標志,類似于第一次開機//判斷獲取到的電平狀態  0-按鍵按下  1-按鍵抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//關閉對應的文件描述符close(fd);   
}

?驅動層代碼:

//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include  <linux/interrupt.h>char k_buf[2]={0};
//定義一個key引腳相關的結構體
struct key_init{int num;        //序號int gpio;       //引腳編號char name[10];  //引腳名稱int irq;        //中斷號unsigned long flag;			//觸發方式
};//聲明變量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//中斷服務函數
static irqreturn_t xxx_key_handler(int irq, void *dev)
{int val;//獲取按鍵的電平狀態-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按鍵按下-0 按鍵抬起-1printk("val:%d\r\n",val);//將獲取到的電平值,保存到k_buf里面k_buf[0] = val + '0';//轉為字符return IRQ_HANDLED;
}//定義一個xxx_read---read-讀操作實現
//應用層的讀函數,對應驅動中的讀函數//1-在驅動層獲取對應引腳狀態//2-將對應的引腳狀態給用戶層
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//將獲取到的數據上傳到應用層->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定義一個xxx_release---close釋放操作實現
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定義file_operations結構體-核心數據結構-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定義miscdevice結構體-核心數據結構
struct miscdevice pdevc=
{.minor=255, // 次設備號.name="key_misc",// 設備名稱.fops=&pfop,//指向文件操作方法集合的指針};//入口函數
static int __init hello_init(void)
{int ret;//1-檢查給定的 GPIO編號是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注冊指定的GPIO引腳資源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-將指定的GPIO引腳設置為輸入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-獲取指定的GPIO的中斷編號keys[0].irq = gpio_to_irq(keys[0].gpio);printk("irq:%d\r\n",keys[0].irq);//5-注冊中斷,綁定對應的中斷服務函數ret=request_irq(keys[0].irq, xxx_key_handler, keys[0].flag,keys[0].name,NULL);if(ret == 0){printk("request_irq success\r\n");}//6-注冊雜項設備驅動模型ret=misc_register(&pdevc);if(ret == 0){printk("misc_register success\r\n");}return 0;
}//出口函數
static void __exit hello_exit(void)
{//1-釋放指定的GPIOgpio_free(keys[0].gpio);//2-釋放指定中斷線free_irq(keys[0].irq,NULL);//3-注銷雜項設備misc_deregister(&pdevc);printk("misc_deregister success\r\n");}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

三、內核定時器---timer?

1、系統定時器

????????系統定時器的作用:定時時間一到,進入到對應的定時處理函數里面。

????????系統定時器有硬件定時器,可以設置的頻率,頻率--(周期):1秒內系統定時觸發中斷的次數,HZ表示,也就是1s內最多可以觸發1000次中斷

如果頻率越高,定時時間間隔越小,導致開銷和電源的消耗越高。頻率高之后,會非常耗電。

注意:系統定時器的計時單位jiffy(系統時間單位)

Linux內核源碼里面專門提供了對應的時間轉換函數:

內核標準計數單位與系統定時器的計時單位的換算有專門的函數

msecs_to_jiffies

功能:

????????將以毫秒為單位的時長轉換為以jiffy為單位的時長—>給內核定時器使用的

頭文件

????????#include <linux/jiffies.h>

原型:

????????unsigned long msecs_to_jiffies(const unsigned int m)

參數

????????m:毫秒數

返回值:

????????相同時長的jiffy的個數

usecs_to_jiffies

功能:

????????將微秒為單位的時長轉換為以jiffy為單位的時長

頭文件

????????#include <linux/jiffies.h>

原型:

????????unsigned long usecs_to_jiffies(const unsigned int m)

參數

????????m:微秒數

返回值:

????????相同時長的jiffy的個數

jiffies

????????在 Linux 內核中,jiffies 是一個全局變量,用于跟蹤自系統啟動以來經過的時鐘滴答數(clock ticks)。每個時鐘滴答代表了一定的時間長度,這個時間長度由內核配置中的 HZ 值決定。HZ 定義了每秒的時鐘滴答數,因此每個滴答的時間長度是 1/HZ 秒。

例如:

定時 10ms?--->jiffies +msecs_to_jiffies(10)

定時 10us?--->jiffies +usecs_to_jiffies(10)

為什么需要加上jiffies?

內核使用 jiffies 來跟蹤時間,并且所有的定時器和延遲操作都是基于 jiffies 的。因此,為了與內核的時間管理系統保持一致,你需要使用 jiffies 作為起點。

2、內核定時器

Linux 內核定時器采用系統時鐘來實現,只需要提供超時時間(定時值)和定時處理函數即可。

內核定時器并不是周期性運行的,超時以后就會自動關閉,因此如果想要實現周期性定時,那么就需要在定時處理函數中重新開啟定時器。

核心數據結構->timer_list

內核源碼里面的基本形式:

struct timer_list
{struct list_head entry;unsigned long expires; /* 定時器超時時間/到期時間, 單位是節拍數 */struct tvec_base *base;void (*function)(unsigned long); /* 定時處理函數指針 */unsigned long data; /* 要傳遞給 function 函數的參數 */int slack;
};

3、相關函數

DEFINE_TIMER

功能

????????靜態初始化定時器,會把核心數據結構進行初始化

頭文件

????????#include <linux/time.h>

宏原型

????????#define DEFINE_TIMER(_name,_function,_expires,_data);

參數

????????_name:定義struct timer_list結構體變量的名稱?(不用取地址)

????????_function:綁定超時處理函數的名字?--->原型:void (*function)(unsigned long);

????????_expires:指的是設置到期時間??注:初始化一個靜態結構體變量時,需要使用常量表達式作為初始化值

????????_data:表示出傳遞給任務函數的參數

返回值: 無

注意:需要定義在全局,不能在入口函數內定義

setup_timer

功能:

????????動態初始化定時器,會把核心數據結構(除到期時間外)進行初始化

原型:

????????#define setup_timer(timer, fn, data)

頭文件

????????#include <linux/time.h>

參數:

????????timer:定時器結構變量地址。這通常是一個指向 struct timer_list 類型的指針

????????fn:超時處理函數--->原型:void (*function)(unsigned long);

????????data :傳遞給超時處理函數的參數

返回值:無

注意:這個宏并沒有初始化?expires成員,所在啟動定時前必須初始化這個成員。

add_timer

功能:

????????注冊指定的timer并啟動,一旦到達定時時間就會觸發定時中斷并執行超時處理函數

頭文件

????????#include <linux/time.h>

宏原型:

????????void add_timer(struct timer_list *timer)?

參數

????????struct timer_list *timer:指向要刪除的 timer_list 結構的指針

返回值:無

del_timer_sync

函數功能:

????????從內核定時器隊列中同步刪除一個定時器

頭文件:

????????#include <linux/time.h>

函數原型:

????????int del_timer_sync(struct timer_list *timer);

參數

????????struct timer_list *timer:指向要刪除的 timer_list 結構的指針

返回值:

????????成功 返回0

????????失敗 返回<0

mod_timer

函數功能:

????????用于修改已經存在于內核定時器隊列中的定時器的到期時間。這意味著如果定時器當前已經在隊列中,mod_timer會更新它的expires字段,以反映新的到期時間,而無需從隊列中移除并重新添加它。

函數原型:

????????int mod_timer(struct timer_list *timer, unsigned long expires);

參數:

????????struct timer_list *timer:指向要修改的timer_list結構的指針。

????????unsigned long?expires:定時器的新到期時間,通常以jiffies加上一個延遲值來表示。

返回值:

????????成功?返回1

????????失敗(如果定時器不在隊列中,也就是之前從未被添加或已經被刪除),則返回0

注意:mod_timer只能修改已經在隊列中的定時器。如果定時器尚未被添加到隊列中,調用mod_timer將導致未定義行為(通常是內核錯誤或崩潰)

4、示例

1:動態初始化定時器并且觸發定時中斷,從而執行超時處理函數

代碼編寫思路:

第一步:入口函數

  1. 動態初始化定時器
  2. 設置到期時間
  3. 注冊指定的timer并啟動

第二步:超時處理函數

打印初始化傳過來的參數

第三步:出口函數

注銷定時器

代碼:

//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>//定義結構體變量
struct timer_list list_timer;//超時處理函數
static void xxx_function(unsigned long data)
{printk("data:%ld\r\n",data);}//入口函數
static int __init hello_init(void)
{//1-動態初始化定時器—動態定義setup_timer(&list_timer,xxx_function,666);//2-設置到期時間list_timer.expires = jiffies + msecs_to_jiffies(5000);//3-注冊指定的timer并啟動add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函數
static void __exit hello_exit(void)
{int ret;//注銷定時器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

修改上面程序,將字符串傳到超時處理函數

代碼:

//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>//定義結構體變量
struct timer_list list_timer;//超時處理函數
static void xxx_function(unsigned long data)
{printk("data:%s\r\n",(char *)data);//%s格式說明符告訴printk函數沿著這個指針一直打印字符,直到遇到null字符(\0)為止}//入口函數
static int __init hello_init(void)
{//1-動態初始化定時器setup_timer(&list_timer,xxx_function,(unsigned long*)"hello_world");//2-設置到期時間list_timer.expires = jiffies + msecs_to_jiffies(5000);//3-注冊指定的timer并啟動add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函數
static void __exit hello_exit(void)
{int ret;//注銷定時器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

2:靜態初始化定時器并且觸發定時中斷,從而執行超時處理函數

代碼編寫思路:

定義全局->靜態初始化定時器

第一步:入口函數

????????注冊指定的timer并啟動

第二步:超時處理函數

????????打印初始化傳過來的參數

第三步:出口函數

????????注銷定時器

代碼:

//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
//定義結構體變量
struct timer_list list_timer;//超時處理函數
static void xxx_function(unsigned long data)
{printk("data:%ld\r\n",data);}//靜態初始化定時器
DEFINE_TIMER(list_timer,xxx_function,HZ*5,666);//入口函數
static int __init hello_init(void)
{//定義對應的時間list_timer.expires = msecs_to_jiffies(5000);//3-注冊指定的timer并啟動add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函數
static void __exit hello_exit(void)
{int ret;//注銷定時器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

四、中斷底半部---tasklet

????????在Linux 內核中,tasklet 是一種輕量級的、基于軟中斷的線程化機制,用于執行延遲的任務或工作。tasklet在中斷上下文中執行,這意味著它們不能睡眠。因此,tasklet中的任務必須是簡短且高效的,不能包含可能導致睡眠的函數調用。

1、核心數據結構->?tasklet_struct

內核源碼里面的基本形式:

struct tasklet_struct{struct tasklet_struct *next; ??//實現鏈表管理,內核自動實現unsigned long state; ??????????//標識tasklet狀態,內核自動實現atomic_t ?count; ??????????????//標識tasklet是否被激活 0激活 ?非0沒有激活void (*func)(unsigned long data); ?//任務函數指針,需要指向自定義的任務函數unsigned long data;};

2、相關函數和

DECLARE_TASKLET

功能

????????靜態定義并初始化一個tasklet對象,處于激活狀態

頭文件

????????#include <linux/interrupt.h>

宏原型

????????#define DECLARE_TASKLET(name,func,data)

參數

????????name:自定義的struct tasklet_struct對象的名稱(不用取地址)

????????func:tasklet對應的函數地址

????????data:傳遞給函數的參數

返回值

tasklet_init

函數功能

????????動態初始化一個已經定義好的struct tasklet_struct對象,使得處于激活狀態

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data)

函數參數

????????struct tasklet_struct*t:指向struct tasklet_struct結構的指針

????????functasklet對應的函數地址

????????unsigned long?data傳遞給函數的參數

函數返回值

tasklet_disable

函數功能

????????指定tasklet的count成員減1 ,試圖使得tasklet處于非激活的狀態

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void tasklet_disable(struct tasklet_struct *t)

函數參數

????????struct tasklet_struct*t:指向struct tasklet_struct結構的指針

函數返回值

tasklet_schedule

函數功能

????????把指定的 tasklet 添加到當前 CPU 的 tasklet 隊列中。在內核的適當時間(通常是當前正在執行的中斷處理程序或軟中斷完成后),tasklet 的執行函數會被調用

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void tasklet_schedule(struct tasklet_struct *t)

函數參數

????????struct tasklet_struct*t:指向struct tasklet_struct結構的指針

函數返回值

注意:tasklet處于激活狀態,調用便執行綁定函數。

tasklet_kill

函數功能

????????取消調度指定的tasklet,將該tasklet從tasklet調度鏈表上進行移除。注意:如果tasklet綁定函數正在執行,則會等待該函數執行完成

頭文件

????????#include <linux/interrupt.h>

函數原型

????????void tasklet_kill(struct tasklet_struct *t)

函數參數

????????struct tasklet_struct*t:指向struct tasklet_struct結構的指針

函數返回值

3、示例

1:動態初始化tasklet_struct,從而執行tasklet處理函數

代碼編寫思路:

第一步:入口函數

  1. 動態初始化tasklet
  2. 調度對應的tasklet

第二步:tasklet處理函數

打印初始化傳過來的參數

第三步:出口函數

注銷tasklet

代碼:

//第一個實例代碼:利用tasklet基礎模塊
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>//定義對應的核心結構體
static struct tasklet_struct list_tasklet;//定義一下中斷底半部的處理函數
static void tasklet_func(unsigned long data)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); //再輸出一下對應的dataprintk("data=%s\r\n",(char *)data);}static int __init xxx_init(void)
{  //初始化核心結構體對象tasklet_init(&list_tasklet,tasklet_func,(unsigned long)"world");//調度一下tasklet_schedule(&list_tasklet);printk("tasklet_init success\r\n");return 0;
} static void __exit xxx_exit(void)
{//注銷對應的tasklettasklet_kill(&list_tasklet);printk("tasklet_kill success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

現象:

[ ??70.752606] tasklet_init success

[ ??70.752662] name:tasklet_func,line:14

[ ??70.752719] data=world

2:靜態初始化tasklet_struct,從而執行tasklet處理函數

代碼編寫思路:

定義全局->靜態初始化tasklet

第一步:入口函數

調度對應的tasklet

第二步:tasklet處理函數

打印初始化傳過來的參數

第三步:出口函數

注銷tasklet

代碼:

#include <linux/module.h>
#include <linux/init.h>#include <linux/interrupt.h>//定義對應的核心結構體
static struct tasklet_struct list_tasklet;//定義一下中斷底半部的處理函數
static void tasklet_func(unsigned long data)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); //再輸出一下對應的dataprintk("data=%s\r\n",(char *)data);}//采用對應的靜態定義的方式實現
DECLARE_TASKLET(list_tasklet,tasklet_func,(unsigned long)"123456");static int __init xxx_init(void)
{     //調度一下tasklet_schedule(&list_tasklet);printk("tasklet_init success\r\n");return 0;
} static void __exit xxx_exit(void)
{//注銷對應的tasklettasklet_kill(&list_tasklet);printk("tasklet_kill success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

現象:

[ ??70.752606] tasklet_init success

[ ??70.752662] name:tasklet_func,line:14

[ ??70.752719] data=123456

動態初始化與靜態初始化的區別?

動態初始化需要在運行時動態創建,而靜態初始化需要在編譯時定義。

注意事項

(1)如果某個非激活的tasklet被調度了,但是在模塊卸載的時候沒有刪除,則內核崩潰。

(2)如果某個非激活的tasklet被調度了,對它做刪除操作,會引起阻塞。

3)如果希望在中斷上下文中實現延時操作,應該使用mdelay()函數-->可以一直占用CPU如果使用msleep()進行休眠會導致內核崩潰。

?五、中斷底半部---工作隊列

????????工作隊列中的任務在內核線程中執行,這意味著它們運行在進程上下文中。因此,工作隊列中的任務可以執行那些可能導致睡眠的函數,如文件I/O操作或內存分配等。由于工作隊列可以睡眠,因此它們非常適合執行那些需要等待資源或進行復雜操作的任務。

1、工作隊列的組成

工作隊列主要由以下幾個部分組成:

工作項(work_struct):工作項是一個結構體,它通常通過INIT_WORK宏進行初始化,并指定一個回調函數(work function)。當工作項被執行時,會調用這個回調函數。通常在共享工作隊列的情況下,不需要創建工作隊列(workqueue_struct)只需要創建工作項(work_struct)

工作隊列(workqueue_struct):工作隊列是一個結構體,它包含了多個工作項的鏈表以及用于保護工作隊列狀態的互斥鎖等。工作隊列可以由多個工作者線程并發訪問和執行其中的工作項。通常在私人工作隊列的情況下,需要創建工作隊列(workqueue_struct)也需要需要創建工作項(work_struct)

工作者線程(worker thread):工作者線程是專門用于執行工作隊列中工作項的內核線程。它會循環地從工作隊列中取出工作項并執行其回調函數。

2、核心數據結構->workqueue_structstruct work_struct

struct workqueue_struct

struct workqueue_struct 負責管理和調度工作項(work_struct),確保這些工作項能夠在適當的時機被內核線程執行。

內核源碼的基本形式:

struct workqueue_struct {struct list_head pwqs;		/* WR: all pwqs of this wq */struct list_head list;		/* PR: list of all workqueues */struct mutex	mutex;		/* protects this wq */int	work_color;	/* WQ: current work color */int	flush_color;	/* WQ: current flush color */
}

?工作隊列的創建通常使用 create_workqueue 函數,銷毀則使用 destroy_workqueue 函數。

struct work_struct

struct work_struct 表示工作隊列中的一個工作項或任務。它包含了執行任務所需的所有信息,并需要被添加到工作隊列 struct workqueue_struct 中才會被調度和執行。

內核源碼的基本形式:

struct work_struct {atomic_long_t data;//用于存儲與工作項相關的數據,內核自動實現struct list_head entry;//用于將工作項鏈接到工作隊列的待處理列表中,內核自動實現work_func_t func;//func 字段指向一個處理函數,當工作項被執行時,該函數將被調用#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};

工作項的初始化通常使用 INIT_WORK 宏,隨后使用 queue_work、queue_work_on、queue_delayed_work 或 queue_delayed_work_on 函數將其添加到工作隊列中

總結:

struct workqueue_struct 是工作隊列的管理和調度結構體,負責管理和調度工作項。

struct work_struct 表示工作隊列中的一個工作項或任務,包含了執行任務所需的所有信息。

3、共享工作隊列

特點:

????????Linux內核會自動為每個處理器創建一個全局的共享工作隊列,驅動模塊只需將自己的工作項(work)掛載到當前處理器的共享工作隊列中。

優點:簡單、快捷,方便,不會占用系統的資源。

缺點:

如果掛載的工作項過多,可能會導致處理延遲,影響任務的時效性。

由于是共享資源,不同驅動模塊的工作項可能會相互影響。

使用流程:

動態或者靜態初始化work_struct

調度對應的work,執行相應的處理函數

注銷work

4、私有工作隊列

特點:

????????私有工作隊列是驅動模塊自建的,僅供該模塊的工作項使用,可以根據任務需求自定義工作隊列的優先級和處理函數。

優點:不受其他模塊工作項的影響,保證了任務的獨立性和時效性。

缺點:需要占用額外的系統資源,需要手動創建和管理工作隊列和工作項。

使用流程:

動態定義并初始化一個工作隊列對象(workqueue_struct)

動態或者靜態初始化(work_struct)?

將一個工作項(work_struct)添加到指定的工作隊列(workqueue_struct)

調度對應的work,執行相應的處理函數

注銷work、workqueue

總結:

①共享工作隊列適用于對時效性要求不高、任務量適中的場景,其實現簡單且資源利用率高。

②私有工作隊列適用于對時效性要求高、需要獨立執行任務的場景,雖然實現復雜且占用資源較多,但能夠提供更好的任務隔離和性能保障。

?

5、相關函數

create_workqueue

功能:

????????為每個處理器靜態定義并初始化一個工作隊列對象(workqueue_struct)

頭文件:

????????#include <linux/workqueue.h>

宏原型:

????????#define create_workqueue(name) ?alloc_workqueue(name, WQ_MEM_RECLAIM, 1)

參數:

????????name:自定義的工作隊列的名稱,是一個字符串

返回值:

????????alloc_workqueue函數會創建一個新的工作隊列,并返回一個指向該工作隊列的struct workqueue_struct指針????????

destroy_workqueue

函數功能:

????????銷毀指定的私有工作隊列(workqueue_struct)

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????void destroy_workqueue(struct workqueue_struct *wq);

函數參數:

????????struct workqueue_struct?*wq:指向目標工作隊列的指針

返回值:無

DECLARE_WORK

功能:

????????用于靜態初始化一個工作項?work_struct 結構體

頭文件:

????????#include <linux/workqueue.h>

宏原型:

????????#define DECLARE_WORK(name, func)

參數:

????????name:想要聲明的 work_struct 變量的名稱

????????func:當工作項被執行時應該調用的處理函數

返回值:無

INIT_WORK

功能:

????????用于動態初始化一個工作項?work_struct 結構體

頭文件:

????????#include <linux/workqueue.h>

宏原型:

????????#define INIT_WORK(_work, _func)

參數:

????????_work:一個指向 work_struct 結構體的指針,表示你想要初始化的工作項。在調用 INIT_WORK 宏之前,通常已經聲明了這個 work_struct 變量??

????????_func:一個函數指針,指向當你將工作項添加到工作隊列并且工作隊列線程變得可用時要執行的處理函數?函數原型:typedef void (*work_func_t)(struct work_struct *work);

返回值:無

queue_work

函數功能:

????????用于將一個工作項(work_struct)添加到指定的工作隊列(workqueue_struct)中以供稍后執行

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work);

函數參數:

????????struct workqueue_struct?*wq指向目標工作隊列的指針

????????struct work_struct *work:指向要添加的工作項的指針。這個工作項在使用前應該通過 INIT_WORK 或 INIT_DELAYED_WORK 宏進行初始化,并指定一個處理函數

返回值:

????????如果工作項成功添加到工作隊列中,函數返回 true。

????????如果由于某種原因(例如工作隊列正在被銷毀)工作項無法被添加,函數返回 false。

schedule_work

函數功能:

????????調度指定的work對象,將該work對象添加到處理器的共享隊列中

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????bool schedule_work(struct work_struct *work)

函數參數:

????????struct work_struct *work:指向要添加的工作項的指針。這個工作項在使用前應該通過? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? INIT_WORK 或 INIT_DELAYED_WORK 宏進行初始化,

????????????????????????????????????????????????并指定一個處理函數

返回值:

????????成功:1 ???失敗:0

bool cancel_work_sync

函數功能:

????????取消指定的普通work對象,將該work對象從當前處理器的共享隊列中移除

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????bool cancel_work_sync(struct work_struct *work)

函數參數:

????????struct work_struct *work:指向要添加的工作項的指針。這個工作項在使用前應該通過 INIT_WORK 或 INIT_DELAYED_WORK 宏進行初始化,并指定一個處理函數

返回值:

????????成功:1 ???失敗:0

6、示例

共享工作隊列的實現

1:動態初始化work_struct,從而執行work處理函數

代碼編寫思路:

定義work核心結構體對象

第一步:入口函數

  1. 動態初始化work
  2. 調度對應的work

第二步:work處理函數

第三步:出口函數

????????注銷work

代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定義work核心結構體對象
static struct work_struct work_lis;//work處理函數
void xxx_workfunc(struct work_struct *work)
{//輸出一下對應的行號printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模塊入口函數
static int __init xxx_init(void)
{//初始化--動態INIT_WORK(&work_lis,xxx_workfunc);//調度schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模塊的出口函數
static void __exit xxx_exit(void)
{//取消對應工作對象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

?

2:靜態初始化work_struct,從而執行work處理函數

代碼編寫思路:

定義work核心結構體對象

定義全局->靜態初始化work

第一步:入口函數

????????調度對應的work

第二步:work處理函數

第三步:出口函數

????????注銷work

代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定義work核心結構體對象
static struct work_struct work_lis;//work處理函數
void xxx_workfunc(struct work_struct *work)
{//輸出一下對應的行號printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//靜態定義
DECLARE_WORK(work_lis,xxx_workfunc);//模塊入口函數
static int __init xxx_init(void)
{//初始化一下--動態//INIT_WORK(&work_lis,xxx_workfunc);//調度一下schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模塊的出口函數
static void __exit xxx_exit(void)
{//取消對應工作對象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

?

私有工作隊列的實現

利用一下私有工作隊列,觀察一下對應的現象

代碼編寫思路:

定義workqueue核心結構體對象

定義work核心結構體對象

第一步:入口函數

  1. 創建對應的工作隊列workqueue
  2. 動態初始化work
  3. 將工作項(work_struct)添加到指定的工作隊列(workqueue_struct)
  4. 調度對應的work,執行相應的處理函數

第二步:work處理函數

第三步:出口函數

????????注銷work、workqueue

代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定義workqueue——stuct核心結構體對象
static struct workqueue_struct *wq;
//定義work_stuct核心結構體對象
static struct work_struct work_lis;//work處理函數
void xxx_workfunc(struct work_struct *work)
{//輸出一下對應的行號printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模塊入口函數
static int __init xxx_init(void)
{bool ret;//創建對應的工作隊列wq=create_workqueue("wque");//初始化一下--動態---將工作隊列上,綁定對應的處理函數INIT_WORK(&work_lis,xxx_workfunc);//將對應的工作對象,添加到對應的工作隊列上ret=queue_work(wq,&work_lis);
if(ret == 1)
{printk("add success\r\n");
}//調度schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模塊的出口函數
static void __exit xxx_exit(void)
{//取消對應工作對象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");//銷毀對應的工作隊列destroy_workqueue(wq);printk(" destroy_workqueue\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");


總結??:

??共享工作隊列??:
??????????利??:零配置、零開銷
??????????弊??:無隔離、延遲不可控
????????? 僅用于??非關鍵低頻任務??
??私有工作隊列??:
??????????利??:低延遲、強隔離、優先級可控
??????????弊??:需手動管理生命周期
????????? ??實時系統/硬件交互/關鍵服務??的基石
?? ??終極決策樹??:

????????任務是否需要硬實時保障? → ??私有隊列 + WQ_HIGHPRI??
????????任務是否與硬件 I/O 相關? → ??私有隊列綁核??
????????任務運行頻率是否高于 1000次/秒? → ??私有隊列調優 max_active??
????????否則 → ??共享隊列省資源?

7、指定延時工作隊列

????????指定延時工作隊列是一種特殊類型的工作隊列,它允許將任務安排在指定的延遲時間后執行。任務在添加到隊列后,不會立即執行,而是等待指定的延遲時間到達后才執行。

①核心數據結構->delayed_work

????????delayed_work 允許你將一個工作項排隊到系統的共享工作隊列中,并指定一個延遲時間,之后該工作項將被調度執行。

內核源碼的基本形式:

struct delayed_work{struct work_struct work;   //代表延時workstruct timer_list timer;      //內核定時器的核心結構
};
②相關函數和宏
DECLARE_DELAYED_WORK

內核源碼的基本形式:

#define DECLARE_DELAYED_WORK(n, f)					\struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

功能:

????????用于靜態初始化一個 delayed_work?結構體

頭文件:

????????#include <linux/workqueue.h>

宏原型:

????????#define DECLARE_DELAYED_WORK(n,f)

參數:

????????n:自定義的delay_work對象的名稱 ??

????????f:已經定義并實現好的delayed_work對象所綁定的任務函數

返回值:無

INIT_DELAYED_WORK

內核源碼的基本形式:

#define INIT_DELAYED_WORK(_work, _func)					\__INIT_DELAYED_WORK(_work, _func, 0)

功能:

????????用于動態初始化一個delayed_work結構體

頭文件:

????????#include <linux/workqueue.h>

宏原型:

????????#define INIT_WORK(_work, _func)

參數:

????????_work:一個指向 delay_work結構體的指針,表示你想要初始化的工作項。在調用 INIT_WORK 宏之前,通常已經聲明了這個 delay_work 變量??

????????_func:已經定義并實現好的delayed_work對象所綁定的任務函數

返回值:無

?

schedule_delayed_work

函數功能:

????????調度指定的delayed_work對象,將該delay_work對象添加到對應的共享工作隊列中

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????bool schedule_delayed_work(struct delayed_work *work,unsigned long delay)

函數參數:

????????struct work_struct *work:指定的delayed_work對象的地址

????????unsigned long delay:延時的時長

返回值:

????????成功:1 ???失敗:0

bool cancel_work_sync

函數功能:

????????取消指定的delayed_work對象,將該delayed_work對象從私有工作隊列中移除

頭文件:

????????#include <linux/workqueue.h>

函數原型:

????????bool cancel_delayed_work_sync(struct delayed_work *dwork);

函數參數:

????????struct delayed_work *dwork:指定的delayed_work對象的地址

返回值:

????????成功:1 ???失敗:0

③示例

1:動態定義并初始化delay_work ?

代碼編寫思路:

定義delay_work核心結構體對象

第一步:入口函數

  1. 動態初始化delay_work
  2. 調度對應的delay_work?

第二步:delay_work處理函數

第三步:出口函數

????????注銷delay_work

代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定義delay_work核心結構體對象
static struct delayed_work work_lis;//delay_work處理函數
void xxx_workfunc(struct work_struct *work)
{//輸出一下對應的行號printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模塊入口函數
static int __init xxx_init(void)
{//初始化一下--動態延時的workINIT_DELAYED_WORK(&work_lis,xxx_workfunc);//調度schedule_delayed_work(&work_lis,msecs_to_jiffies(3000));printk("schedule_delayed_work success\r\n");return 0;
}//模塊的出口函數
static void __exit xxx_exit(void)
{//取消對應工作對象cancel_delayed_work_sync(&work_lis);printk("cancel_delayed_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

2:靜態定義并初始化delay_wrok

代碼編寫思路:

定義delay_work核心結構體對象

定義全局->靜態初始化delay_work

第一步:入口函數

調度對應的delay_work

第二步:delay_work處理函數

第三步:出口函數

注銷delay_work

代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定義delay_work核心結構體對象
static struct delayed_work work_lis;//delay_work處理函數
void xxx_workfunc(struct work_struct *work)
{//輸出一下對應的行號printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//靜態定義
DECLARE_DELAYED_WORK(work_lis,xxx_workfunc);//模塊入口函數
static int __init xxx_init(void)
{//調度schedule_delayed_work(&work_lis,msecs_to_jiffies(3000));printk("schedule_delayed_work success\r\n");return 0;
}//模塊的出口函數
static void __exit xxx_exit(void)
{//取消對應工作對象cancel_delayed_work_sync(&work_lis);printk("cancel_delayed_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

?

六、按鍵消抖的實現

思路1

中斷頂半部 +?定時器?(抖動)???

卡死的原因:定時器與中斷的優先級別都很高,由于按鍵的抖動多次觸發了中斷,并且也進行了定時,此時內核無法判斷優先執行哪個,從而導致內核崩潰。

分析思路:應用程序app + 中斷控制

應用層代碼:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//調用一下open函數fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//獲取驅動層硬件的電平狀態read(fd,r_buf,1);//避免重復顯示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//標志,類似于第一次開機//判斷獲取到的電平狀態  0-按鍵按下  1-按鍵抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//關閉對應的文件描述符close(fd);   
}

驅動層代碼:

//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>char k_buf[2]={0};struct key_init{int num;			//序號int gpio;			//引腳編號char name[10];		//引腳名稱int irq;			//中斷號unsigned long flag;//中斷的觸發方式struct timer_list list_timer;  //定時器核心結構體};//聲明變量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//超時處理函數
static void xxx_function(unsigned long data)
{int val;//獲取按鍵的電平狀態-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按鍵按下-0 按鍵抬起-1printk("val:%d\r\n",val);//將獲取到的電平值,保存到k_buf里面k_buf[0] = val + '0';//轉為字符printk("data:%ld\r\n",data);}//中斷服務函數
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//設置到期時間//keys[0].list_timer.expires = jiffies + msecs_to_jiffies(30);//注冊指定的timer并啟動//add_timer(&keys[0].list_timer);//重新啟動定時器mod_timer(&(p->list_timer), jiffies+msecs_to_jiffies(20));printk("add success\r\n");return IRQ_HANDLED;
}//定義一個xxx_read---read-讀操作實現
//應用層的讀函數,對應驅動中的讀函數//1-在驅動層獲取對應引腳狀態//2-將對應的引腳狀態給用戶層
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//將獲取到的數據上傳到應用層->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定義一個xxx_release---close釋放操作實現
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定義file_operations結構體-核心數據結構-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定義miscdevice結構體-核心數據結構
struct miscdevice pdevc=
{.minor=255, // 次設備號.name="key_misc",// 設備名稱.fops=&pfop,//指向文件操作方法集合的指針};//入口函數
static int __init hello_init(void)
{int ret;//1-檢查給定的GPIO編號是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注冊指定的GPIO引腳資源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-將指定的GPIO引腳設置為輸入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-獲取指定的GPIO的中斷編號keys[0].irq = gpio_to_irq(keys[0].gpio);printk("irq:%d\r\n",keys[0].irq);//5-注冊中斷,綁定對應的中斷服務函數ret=request_irq(keys[0].irq, xxx_key_handler, keys[0].flag,keys[0].name,&keys[0]);if(ret < 0){printk("error request_irq!\r\n");}else {printk("request_irq success\r\n");}//6-動態初始化定時器setup_timer(&keys[0].list_timer,xxx_function,666);//7-注冊雜項設備驅動模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函數
static void __exit hello_exit(void)
{int ret;//注銷定時器ret = del_timer_sync(&keys[0].list_timer);if(ret == 0){printk("del success\r\n");}//釋放指定中斷線free_irq(keys[0].irq,NULL);//釋放指定的GPIOgpio_free(keys[0].gpio);//注銷雜項設備misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

思路2

中斷頂半部 +?中斷底半部tasklet + 延時mdelay

應用層代碼:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//調用一下open函數fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//獲取驅動層硬件的電平狀態read(fd,r_buf,1);//避免重復顯示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//標志,類似于第一次開機//判斷獲取到的電平狀態  0-按鍵按下  1-按鍵抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//關閉對應的文件描述符close(fd);   
}

驅動層代碼:

//死等 + 中斷頂半部 + 中斷底半部tasklet(消抖)
//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>char k_buf[2]={0};struct key_init{int num;			//序號int gpio;			//引腳編號char name[10];		//引腳名稱int irq;			//中斷號unsigned long flag;//中斷的觸發方式struct tasklet_struct list_task;  //tasklet核心結構體};//聲明變量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//處理函數
static void xxx_function(unsigned long data)
{int val;//消抖---占用CPU->再次觸發也不會執行mdelay(20);//獲取按鍵的電平狀態-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按鍵按下-0 按鍵抬起-1printk("val:%d\r\n",val);//將獲取到的電平值,保存到k_buf里面k_buf[0] = val + '0';//轉為字符printk("data:%ld\r\n",data);}//中斷服務函數
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//調度指定的tasklet---即刻執行任務函數tasklet_schedule(&keys[0].list_task);return IRQ_HANDLED;
}//定義一個xxx_read---read-讀操作實現
//應用層的讀函數,對應驅動中的讀函數//1-在驅動層獲取對應引腳狀態//2-將對應的引腳狀態給用戶層
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//將獲取到的數據上傳到應用層->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定義一個xxx_release---close釋放操作實現
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定義file_operations結構體-核心數據結構-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定義miscdevice結構體-核心數據結構
struct miscdevice pdevc=
{.minor=255, // 次設備號.name="key_misc",// 設備名稱.fops=&pfop,//指向文件操作方法集合的指針};//入口函數
static int __init hello_init(void)
{int ret;//1-檢查給定的 GPIO編號是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注冊指定的GPIO引腳資源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-將指定的GPIO引腳設置為輸入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-獲取對應的中斷編號	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("keys[0].irq=%d\r\n",keys[0].irq);//5-注冊中斷服務函數	ret=request_irq(keys[0].irq,xxx_key_handler,keys[0].flag,keys[0].name,&keys[0]);if (ret==0) {printk(" request_irq success\n");}//6-動態初始化tasklettasklet_init(&keys[0].list_task,xxx_function,666);//7-注冊雜項設備驅動模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函數
static void __exit hello_exit(void)
{//取消調度指定的tasklettasklet_kill(&keys[0].list_task);//釋放中斷free_irq(keys[0].irq, &keys[0]);printk("IRQ freed\n");//釋放GPIOgpio_free(keys[0].gpio);printk("GPIO freed\n");//注銷雜項設備misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

思路3

中斷頂半部 + 中斷底半部work + 睡眠msleep

應用層代碼:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//調用一下open函數fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//獲取驅動層硬件的電平狀態read(fd,r_buf,1);//避免重復顯示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//標志,類似于第一次開機//判斷獲取到的電平狀態  0-按鍵按下  1-按鍵抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//關閉對應的文件描述符close(fd);   
}

驅動層代碼:

// 中斷頂半部 + 中斷底半部work
//驅動包含的頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/workqueue.h>char k_buf[2]={0};struct key_init{int num;			//序號int gpio;			//引腳編號char name[10];		//引腳名稱int irq;			//中斷號unsigned long flag;//中斷的觸發方式struct work_struct work_lis; //work核心數據結構};//聲明變量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//處理函數
static void xxx_workfunc(struct work_struct *work)
{int val;//延時函數msleep(20);//獲取按鍵的電平狀態-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按鍵按下-0 按鍵抬起-1printk("val:%d\r\n",val);//將獲取到的電平值,保存到k_buf里面k_buf[0] = val + '0';//轉為字符}//中斷服務函數
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//調度schedule_work(&keys[0].work_lis);printk("schedule_work success\r\n");return IRQ_HANDLED;
}//定義一個xxx_read---read-讀操作實現
//應用層的讀函數,對應驅動中的讀函數//1-在驅動層獲取對應引腳狀態//2-將對應的引腳狀態給用戶層
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//將獲取到的數據上傳到應用層->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定義一個xxx_release---close釋放操作實現
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定義file_operations結構體-核心數據結構-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定義miscdevice結構體-核心數據結構
struct miscdevice pdevc=
{.minor=255, // 次設備號.name="key_misc",// 設備名稱.fops=&pfop,//指向文件操作方法集合的指針};//入口函數
static int __init hello_init(void)
{int ret;//1-檢查給定的 GPIO編號是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注冊指定的GPIO引腳資源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-將指定的GPIO引腳設置為輸入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-獲取對應的中斷編號	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("irq=%d\r\n",keys[0].irq);//5-注冊中斷服務函數	ret=request_irq(keys[0].irq,xxx_key_handler,keys[0].flag,keys[0].name,&keys[0]);if (ret==0) {printk(" request_irq success\n");}//6-初始化--動態INIT_WORK(&keys[0].work_lis,xxx_workfunc);//7-注冊雜項設備驅動模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函數
static void __exit hello_exit(void)
{//取消對應工作對象cancel_work_sync(&keys[0].work_lis);//釋放中斷free_irq(keys[0].irq, &keys[0]);printk("IRQ freed\n");//釋放GPIOgpio_free(keys[0].gpio);printk("GPIO freed\n");//注銷雜項設備misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}
//標記函數
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

現象:

?

思路4

中斷頂半部 + 中斷底半部delay_work+延時mdelay

注意:調度時延時時間需要設置為0

應用層代碼:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//調用一下open函數fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//獲取驅動層硬件的電平狀態read(fd,r_buf,1);//避免重復顯示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//標志,類似于第一次開機//判斷獲取到的電平狀態  0-按鍵按下  1-按鍵抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//關閉對應的文件描述符close(fd);   
}

驅動層代碼:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
//定義一個key引腳相關的結構體
struct key_init{int num;            //序號int gpio;           //引腳編號char name[10];      //引腳名稱int irq;            //中斷號unsigned long flags;//中斷的觸發方式struct delayed_work work_lis;;  //中斷底半部核心結構體--延時的工作隊列
};//聲明變量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING},{},
};//定義一個k_buf的數組
static char k_buf[2]={0};//work處理函數
static void xxx_workfunc(struct work_struct *work)
{//獲取對應引腳的電平狀態int s;//延時函數msleep(20);s=gpio_get_value(keys[0].gpio);  //按下按鍵的時候,對應的引腳電平是低電平//輸出觀察一下printk("s=%d\r\n",s);//將對應的s里面的值,保存到k_buf里面k_buf[0]= s + '0';//輸出觀察一下printk("k_buf[0]=%c\r\n",k_buf[0]);printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); }//中斷處理函數
static irqreturn_t xxx_handler_key(int irq, void *dev)
{//接收一下,傳遞進來的結構體對象struct key_init *p =(struct key_init *)dev;//輸出觀察一下printk("p->gpio=%d , p->name=%s\r\n",p->gpio,p->name);//啟動中斷底半部--調用對應的workschedule_delayed_work(&keys[0].work_lis,msecs_to_jiffies(0));//提示一下printk("schedule_work success\r\n");return IRQ_HANDLED;
}//定義一個xxx_read函數
//1、利用for循環獲取對應引腳狀態
//2、將對應的引腳狀態給用戶層
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{    int ret;printk("file:%s,name:%s,line:%d\r\n",__FILE__,__FUNCTION__,__LINE__); //將對應的k_buf里面保存的數據上報給appret=copy_to_user(buf,k_buf,1);printk("copy_to_user success\r\n");return 0;
}//定義一個xxx_release
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); return 0;
}//定義一個文件結合
static struct file_operations pfop ={.read = xxx_read,.release =xxx_release,
};//定義核心結構體對象
static struct miscdevice pdevc ={.minor = 255,.name  = "key_misc",.fops  = &pfop,
};//入口函數
static int __init xxx_init(void)
{   int ret;//判斷對應的引腳是否合法--對應的引腳是否被占用ret=gpio_is_valid(keys[0].gpio);printk("ret=%d\r\n",ret);//注冊對應的gpio口ret=gpio_request(keys[0].gpio,keys[0].name);printk("ret=%d\r\n",ret);//獲取對應的中斷編號	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("keys[0].irq=%d\r\n",keys[0].irq);//注冊中斷APIret=request_irq(keys[0].irq,xxx_handler_key,keys[0].flags,keys[0].name,&keys[0]);if(ret == 0){printk("request_irq success\r\n");}//初始化一下--動態//INIT_WORK(&keys[0].work_lis,xxx_workfunc);INIT_DELAYED_WORK(&keys[0].work_lis,xxx_workfunc);//5--注冊雜項設備--注冊函數APIret=misc_register(&pdevc);if(ret == 0){printk("misc_register success\r\n");}return 0;
}//出口函數
static void __exit xxx_exit(void)
{//移除對應的中斷底半部cancel_delayed_work_sync(&keys[0].work_lis);printk("cancel_work_sync success\r\n");//釋放中斷
free_irq(keys[0].irq,&keys[0]);//釋放對應的GPIO口gpio_free(keys[0].gpio);//注銷函數misc_deregister(&pdevc);printk("misc_deregister success\r\n");	}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86037.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86037.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86037.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端跨域解決方案(7):Node中間件

1 Node 中間件核心 1.1 為什么開發環境需要 Node 代理&#xff1f; 在前端開發中&#xff0c;我們常遇到&#xff1a;前端運行在localhost:3000&#xff0c;后端 API 在localhost:4000&#xff0c;跨域導致請求失敗。而傳統解決方案有以下局限性&#xff1a; 修改后端 CORS 配…

iwebsec靶場-文件上傳漏洞

01-前端JS過濾繞過 1&#xff0c;查看前端代碼對文件上傳的限制策略 function checkFile() { var file document.getElementsByName(upfile)[0].value; if (file null || file "") { alert("你還沒有選擇任何文件&a…

GitHub 趨勢日報 (2025年06月23日)

&#x1f4ca; 由 TrendForge 系統生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日報中的項目描述已自動翻譯為中文 &#x1f4c8; 今日獲星趨勢圖 今日獲星趨勢圖 390 suna 387 system-prompts-and-models-of-ai-tools 383 Web-Dev-For-Beginners…

告別水印煩惱,一鍵解鎖高清無痕圖片與視頻!

在這個數字化飛速發展的時代&#xff0c;無論是設計小白還是創意達人&#xff0c;都可能遇到這樣的困擾&#xff1a;心儀的圖片或視頻因水印而大打折扣&#xff0c;創意靈感因水印而受限。別急&#xff0c;今天就為大家帶來幾款神器&#xff0c;讓你輕松告別水印煩惱&#xff0…

LangChain4j在Java企業應用中的實戰指南:構建RAG系統與智能應用-2

LangChain4j在Java企業應用中的實戰指南&#xff1a;構建RAG系統與智能應用-2 開篇&#xff1a;LangChain4j框架及其在Java生態中的定位 隨著人工智能技術的快速發展&#xff0c;尤其是大語言模型&#xff08;Large Language Models, LLMs&#xff09;的廣泛應用&#xff0c;…

Cola StateMachine 的無狀態(Stateless)特性詳解

Cola StateMachine 的無狀態&#xff08;Stateless&#xff09;特性詳解 在現代分布式系統中&#xff0c;無狀態設計是構建高可用、可擴展服務的關鍵原則之一。Cola StateMachine 作為一款輕量級的狀態機框架&#xff0c;通過其獨特的設計理念實現了良好的無狀態特性。本文將深…

使用事件通知來處理頁面回退時傳遞參數和賦值問題

背景。uniapp開發微信小程序。在當前頁面需要選擇條件&#xff0c;如選擇城市。會打開新的頁面。此時選擇之后需要關閉頁面回到當初的頁面。但問題出現了。onLoad等事件是不會加載的。相關鏈接。uniapp頁面通訊說明使用事件通知來處理頁面回退時傳遞參數和賦值問題 頁面之間的…

騰訊云COS“私有桶”下,App如何安全獲得音頻調用流程

流程圖 #mermaid-svg-Phy4VCltBRZ90UH8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Phy4VCltBRZ90UH8 .error-icon{fill:#552222;}#mermaid-svg-Phy4VCltBRZ90UH8 .error-text{fill:#552222;stroke:#552222;}#me…

基于深度學習的側信道分析(DLSCA)Python實現(帶測試)

一、DLSCA原理介紹 基于深度學習的側信道分析(DLSCA)是一種結合深度神經網絡與側信道分析技術的密碼分析方法。該方法利用深度學習模型從能量消耗、電磁輻射等側信道信息中提取與密鑰相關的特征模式。相比傳統分析方法&#xff0c;DLSCA能夠自動學習復雜的特征關系&#xff0c…

云原生 CAD 讓制造業設計協同更便捷

隨著互聯網、云計算技術的突飛猛進&#xff0c;CAD向著網絡化、協同化的方向快速發展&#xff0c;云CAD軟件逐漸映入人們的眼簾。云原生CAD不僅打破了傳統CAD軟件對硬件配置的依賴&#xff0c;更以數據驅動的協同創新模式&#xff0c;重塑了制造業的產品研發流程與組織協作形態…

Docker容器核心操作指南:`docker run`參數深度解析

技術聚焦 作為容器化技術的起點&#xff0c;docker run命令承擔著90%的容器創建工作。其關鍵參數-d&#xff08;后臺模式&#xff09;與-it&#xff08;交互模式&#xff09;的合理運用&#xff0c;直接影響容器行為模式與運維效率。本文將深度拆解兩大模式的應用場景與…

基于單片機的語音控制設計(論文)

摘要 自然語音作為人機交互在目前得以廣泛的應用以及極大的發展前景。該設計介紹了基于非指定人語音芯片LD3320的語音控制器結構及其實現語音控制的方法。該語音控制器利用STM32F103C8T6單片機作為主要控制器&#xff0c;控制芯片對輸入的進行語音識別并處理&#xff0c;根據語…

【論文閱讀 | CVPRW 2023 |CSSA :基于通道切換和空間注意力的多模態目標檢測】

論文閱讀 | CVPRW 2023 |CSSA &#xff1a;基于通道切換和空間注意力的多模態目標檢測 1.摘要&&引言2.方法2.1 框架概述2.2 通道切換通道注意力2.3 空間注意力 3. 實驗3.1 實驗設置3.1.1 數據集3.1.2 實現細節3.1.3 評估指標 3.2 對比研究3.2.1 定量結果3.2.2 定性結果…

《前端資源守衛者:SRI安全防護全解析》

SRI&#xff08;子資源完整性&#xff09;作為守護前端安全的隱形盾牌&#xff0c;以精妙的技術設計構建起資源驗證防線。深入理解其工作邏輯與配置方法&#xff0c;是每位前端開發者筑牢應用安全的必修課。 SRI的核心價值&#xff0c;在于為外部資源打造獨一無二的“數字身份…

項目需求評審報告參考模板

該文檔是需求評審報告模板 內容涵蓋評審基礎信息,如項目名稱、評審時間、地點、級別、方式等;包含評審簽到表,記錄角色、部門、職務、姓名等信息;還有評審工作量統計相關內容;以及評審問題跟蹤表,記錄問題描述、狀態、解決人及時限等,還附有填表說明,對評審適用范圍、工…

從依賴進口到自主創新:AI 電子設計系統如何重塑 EDA 全流程

EDA全稱是Electronic Design Automation&#xff0c;即電子設計自動化&#xff0c;是利用計算機軟件完成電路設計、仿真、驗證等流程的設計工具&#xff0c;貫穿于芯片和板級電路設計、制造、測試等環節&#xff0c;是不可或缺的基礎設計工具。 EDA與電子材料、裝備是電子信…

前端工程化之微前端

微前端 微前端基本知識主要的微前端框架iframe優點&#xff1a;缺點&#xff1a; single-spa示例主應用spa-root-config.jsmicrofrontend-layout.htmlindex.ejs 子應用spa-react-app2.jsroot.component.js 修改路由spa-demo/microfrontend-layout.htmlspa-demo/react-app1/webp…

MemcacheRedis--緩存服務器理論

Memcached/redis是高性能的分布式內存緩存服務器,通過緩存數據庫查詢結果&#xff0c;減少數據庫訪問次數&#xff0c;以提高動態Web等應用的速度、 提高可擴展性。 緩存服務器作用: 加快訪問速度 ,緩解數據庫壓力 1. memcached&#xff08;單節點在用&#xff09; 1.1 特點 1…

【stm32】標準庫學習——I2C

目錄 一、I2C 1.I2C簡介 2.MPU6050參數 3.I2C時序基本單元 二、I2C外設 1.I2C外設簡介 2.配置I2C基本結構 3.初始化函數模板 4.常用函數 一、I2C 1.I2C簡介 本節課使用的是MPU6050硬件外設 2.MPU6050參數 3.I2C時序基本單元 這里發送應答是指主機發送&#xff0c;即…

HSA22HSA29美光固態芯片D8BJVC8BJW

HSA22HSA29美光固態芯片D8BJVC8BJW 美光固態芯片D8BJVC8BJW系列&#xff1a;技術革新與行業應用深度解析 一、技術解析&#xff1a;核心架構與創新突破 美光D8BJVC8BJW系列固態芯片&#xff08;如MT29F8T08EQLEHL5-QAES:E、MT29F512G08CUCABH3-12Q等&#xff09;的技術競爭力…