0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux驅(qū)動(dòng)基礎(chǔ)知識(shí)科普

xCb1_yikoulinux ? 來源:一口Linux ? 作者: 土豆居士 ? 2022-05-25 12:35 ? 次閱讀

2a7bdafa-dbe2-11ec-ba43-dac502259ad0.png

驅(qū)動(dòng)認(rèn)知

1. 什么是驅(qū)動(dòng)

驅(qū)動(dòng)就是對(duì)底層硬件設(shè)備的操作進(jìn)行封裝,并向上層提供函數(shù)接口。

設(shè)備分類:linux系統(tǒng)將設(shè)備分為3類:字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備。

  • 字符設(shè)備:指只能一個(gè)字節(jié)一個(gè)字節(jié)讀寫的設(shè)備,不能隨機(jī)讀取設(shè)備內(nèi)存中的某一數(shù)據(jù),讀取數(shù)據(jù)需要按照先后順序。字符設(shè)備是面向流的設(shè)備,常見的字符設(shè)備有鼠標(biāo)、鍵盤、串口、控制臺(tái)和LED設(shè)備等,字符設(shè)備驅(qū)動(dòng)程序通常至少要實(shí)現(xiàn)open、close、read和write的系統(tǒng)調(diào)用,字符終端(/dev/console)和串口(/dev/ttyS0以及類似設(shè)備)就是兩個(gè)字符設(shè)備,它們能很好的說明“流”這種抽象概念。
  • 塊設(shè)備:指可以從設(shè)備的任意位置讀取一定長度數(shù)據(jù)的設(shè)備。塊設(shè)備包括硬盤、磁盤、U盤和SD卡等。
  • 網(wǎng)絡(luò)設(shè)備:網(wǎng)絡(luò)設(shè)備可以是一個(gè)硬件設(shè)備,如網(wǎng)卡; 但也可以是一個(gè)純粹的軟件設(shè)備,比如回環(huán)接口(lo).一個(gè)網(wǎng)絡(luò)接口負(fù)責(zé)發(fā)送和接收數(shù)據(jù)報(bào)文。2ab7058a-dbe2-11ec-ba43-dac502259ad0.png

我們來舉一個(gè)例子來說一下整體的調(diào)用過程

  1. 在上層我們調(diào)用 c語言 open函數(shù) open("/dev/pin4",O_RDWR); 調(diào)用/dev下的pin4以可讀可寫的方式打開,**==對(duì)于上層open調(diào)用到內(nèi)核時(shí)會(huì)發(fā)生一次軟中斷中斷號(hào)是0X80,從用戶空間進(jìn)入到內(nèi)核空間==**
  2. open會(huì)調(diào)用到system_call(內(nèi)核函數(shù)),system_call會(huì)根據(jù)/dev/pin4設(shè)備名,去找出你要的設(shè)備號(hào)。
  3. 再調(diào)到虛擬文件VFS為了上層調(diào)用到確切的硬件統(tǒng)一化),調(diào)用VFS里的sys_open,sys_open會(huì)找到在驅(qū)動(dòng)鏈表里面,根據(jù)主設(shè)備號(hào)和次設(shè)備號(hào)找到引腳4里的open函數(shù),我們?cè)谝_4里的open是對(duì)寄存器操作
2aeadafe-dbe2-11ec-ba43-dac502259ad0.png在這里插入圖片描述

我們寫驅(qū)動(dòng)無非就是做添加驅(qū)動(dòng)添加驅(qū)動(dòng)做哪些事呢?

  1. 設(shè)備名
  2. 設(shè)備號(hào)
  3. 設(shè)備驅(qū)動(dòng)函數(shù) (操作寄存器 來驅(qū)動(dòng) IO口)

==綜上所述==如果想要打開dev下面的pin4引腳,過程是:用戶態(tài)調(diào)用open“/de/pin4”,O_RDWR),對(duì)于內(nèi)核來說,上層調(diào)用open函數(shù)會(huì)觸發(fā)一個(gè)軟中斷(系統(tǒng)調(diào)用專用,中斷號(hào)是0x80,0x80代表發(fā)生了一個(gè)系統(tǒng)調(diào)用),系統(tǒng)進(jìn)入內(nèi)核態(tài),并走到system_call,可以認(rèn)為這個(gè)就是此軟中斷的中斷服務(wù)程序入口,然后通過傳遞過來的系統(tǒng)調(diào)用號(hào)來決定調(diào)用相應(yīng)的系統(tǒng)調(diào)用服務(wù)程序(在這里是調(diào)用VFS中的sys_open)。sys_open會(huì)在內(nèi)核的驅(qū)動(dòng)鏈表里面根據(jù)設(shè)備名和設(shè)備號(hào)查找到相關(guān)的驅(qū)動(dòng)函數(shù)每一個(gè)驅(qū)動(dòng)函數(shù)是一個(gè)節(jié)點(diǎn)),**==驅(qū)動(dòng)函數(shù)里面有通過寄存器操控IO口的代碼,進(jìn)而可以控制IO口實(shí)現(xiàn)相關(guān)功能==**。

2. 各分態(tài)的詳解

用戶態(tài):

  • 是指用戶編寫程序、運(yùn)行程序的層面,用戶態(tài)在開發(fā)時(shí)需要C的基礎(chǔ)和C庫,C庫講到文件,進(jìn)程,進(jìn)程間通信,線程,網(wǎng)絡(luò),界面(GTk)。C庫(是linux標(biāo)準(zhǔn)庫一定有):就是Clibary,提供了程序支配內(nèi)核干活的接口,調(diào)用的open,read,write,fork,pthread,socket由此處封裝實(shí)現(xiàn),由寫的應(yīng)用程序調(diào)用,C庫中的各種API調(diào)用的是內(nèi)核態(tài),支配內(nèi)核干活。

內(nèi)核態(tài):

  • 用戶要使用某個(gè)硬件設(shè)備時(shí),需要內(nèi)核態(tài)的設(shè)備驅(qū)動(dòng)程序,進(jìn)而驅(qū)動(dòng)硬件干活,就比如之前文章里面所提到的wiringPi庫,就是提供了用戶操控硬件設(shè)備的接口,在沒有wiringPi庫時(shí)就需要自己實(shí)現(xiàn)wiringPi庫的功能,就是自己寫設(shè)備驅(qū)動(dòng)程序。這樣當(dāng)我們拿到另一種類型的板子時(shí),同樣也可以完成開發(fā)。

  • 在linux中一切皆文件,各種的文件和設(shè)備(比如:鼠標(biāo)、鍵盤、屏幕、flash、內(nèi)存、網(wǎng)卡、如下圖所示:)都是文件,那既然是文件了,就可以使用文件操作函數(shù)來操作這些設(shè)備。2b23bd24-dbe2-11ec-ba43-dac502259ad0.png

  • 有一個(gè)問題,open、read等這些文件操作函數(shù)是如何知道打開的文件是哪一種硬件設(shè)備呢?①在open函數(shù)里面輸入對(duì)應(yīng)的文件名,進(jìn)而操控對(duì)應(yīng)的設(shè)備。②通過 ==設(shè)備號(hào)(主設(shè)備號(hào)和次設(shè)備號(hào))== 。除此之外我們還要了解這些驅(qū)動(dòng)程序的位置,和如何實(shí)現(xiàn)這些驅(qū)動(dòng)程序,每一種硬件設(shè)備對(duì)應(yīng)不同的驅(qū)動(dòng)(這些驅(qū)動(dòng)有我們自己來實(shí)現(xiàn))。

  • Linux的設(shè)備管理是和文件系統(tǒng)緊密結(jié)合的,各種設(shè)備都以文件的形式存放在/dev目錄下,稱為 ==設(shè)備文件==。應(yīng)用程序可以打開、關(guān)閉和讀寫這些設(shè)備文件,完成對(duì)設(shè)備的操作,就像操作普通的數(shù)據(jù)文件一樣。為了管理這些設(shè)備,系統(tǒng)為設(shè)備編了號(hào)每個(gè)設(shè)備號(hào)又分為==主設(shè)備號(hào)== 和 ==次設(shè)備號(hào)==(如下圖所示:)。2b46bc84-dbe2-11ec-ba43-dac502259ad0.png


    主設(shè)備號(hào)用來區(qū)分不同種類的設(shè)備,而
    次設(shè)備號(hào)
    用來區(qū)分同一類型的多個(gè)設(shè)備。對(duì)于常用設(shè)備,Linux有約定俗成的編號(hào),如硬盤的主設(shè)備號(hào)是3。一個(gè)字符設(shè)備或者塊設(shè)備都有一個(gè)主設(shè)備號(hào)和次設(shè)備號(hào)。**==主設(shè)備號(hào)和次設(shè)備號(hào)統(tǒng)稱為設(shè)備號(hào)==**。

    主設(shè)備號(hào)用來表示一個(gè)特定的驅(qū)動(dòng)程序。
    次設(shè)備號(hào)用來表示使用該驅(qū)動(dòng)程序的各設(shè)備。

    例如一個(gè)嵌入式系統(tǒng),有兩個(gè)LED指示燈,LED燈需要獨(dú)立的打開或者關(guān)閉。那么,可以寫一個(gè)LED燈的字符設(shè)備驅(qū)動(dòng)程序,可以將其主設(shè)備號(hào)注冊(cè)成5號(hào)設(shè)備,次設(shè)備號(hào)分別為1和2。這里,次設(shè)備號(hào)就分別表示兩個(gè)LED燈。

==驅(qū)動(dòng)鏈表==

管理所有設(shè)備的驅(qū)動(dòng),添加或查找
添加是發(fā)生在我們編寫完驅(qū)動(dòng)程序,加載到內(nèi)核。
查找是在調(diào)用驅(qū)動(dòng)程序,由應(yīng)用層用戶空間去查找使用open函數(shù)。

驅(qū)動(dòng)插入鏈表的順序由設(shè)備號(hào)檢索,就是說主設(shè)備號(hào)和次設(shè)備號(hào)除了能區(qū)分不同種類的設(shè)備和不同類型的設(shè)備,還能起到將驅(qū)動(dòng)程序加載到鏈表的某個(gè)位置,在下面介紹的驅(qū)動(dòng)代碼的開發(fā)無非就是添加驅(qū)動(dòng)(添加設(shè)備號(hào)、設(shè)備名和設(shè)備驅(qū)動(dòng)函數(shù))和調(diào)用驅(qū)動(dòng)。

  • system_call函數(shù)是怎么找到詳細(xì)的系統(tǒng)調(diào)用服務(wù)例程的呢?通過系統(tǒng)調(diào)用號(hào)查找系統(tǒng)調(diào)用表sys_call_table! 軟中斷指令I(lǐng)NT 0x80運(yùn)行時(shí),系統(tǒng)調(diào)用號(hào)會(huì)被放入 eax寄存器中,system_call函數(shù)能夠讀取eax寄存器獲取,然后將其乘以4,生成偏移地址,然后以sys_call_table為基址。基址加上偏移地址,就能夠得到詳細(xì)的系統(tǒng)調(diào)用服務(wù)例程的地址了!然后就到了系統(tǒng)調(diào)用服務(wù)例程了。

補(bǔ)充:

  1. 每個(gè)系統(tǒng)調(diào)用都對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用號(hào),而系統(tǒng)調(diào)用號(hào)就對(duì)應(yīng)內(nèi)核中的相應(yīng)處理函數(shù)。
  2. 所有系統(tǒng)調(diào)用都是通過中斷0x80來觸發(fā)的。
  3. 使用系統(tǒng)調(diào)用時(shí),通過eax 寄存器將系統(tǒng)調(diào)用號(hào)傳遞到內(nèi)核,系統(tǒng)調(diào)用的入?yún)⑼ㄟ^ebx、ecx……依次傳遞到內(nèi)核
  4. 和函數(shù)一樣,系統(tǒng)調(diào)用的返回值保存在eax中,所有要從eax中取出

3. 字符設(shè)備驅(qū)動(dòng)工作原理

字符設(shè)備驅(qū)動(dòng)工作原理在linux的世界里一切皆文件,所有的硬件設(shè)備操作到應(yīng)用層都會(huì)被抽象成文件的操作。我們知道如果應(yīng)用層要訪問硬件設(shè)備,它必定要調(diào)用到硬件對(duì)應(yīng)的驅(qū)動(dòng)程序。Linux內(nèi)核有那么多驅(qū)動(dòng)程序,應(yīng)用怎么才能精確的調(diào)用到底層的驅(qū)動(dòng)程序呢?

==必須知道的知識(shí):==

  1. 在Linux文件系統(tǒng)中,每個(gè)文件都用一個(gè) struct inode結(jié)構(gòu)體來描述,這個(gè)結(jié)構(gòu)體記錄了這個(gè)文件的所有信息,例如文件類型,訪問權(quán)限等。

  2. 在linux操作系統(tǒng)中,每個(gè)驅(qū)動(dòng)程序在應(yīng)用層的/dev目錄或者其他如/sys目錄下都會(huì)有一個(gè)文件與之對(duì)應(yīng)。

  3. 在linux操作系統(tǒng)中, 每個(gè)驅(qū)動(dòng)程序都有一個(gè)設(shè)備號(hào)。

  4. 在linux操作系統(tǒng)中,每打開一次文件,Linux操作系統(tǒng)會(huì)在VFS層分配一個(gè)struct file結(jié)構(gòu)體來描述打開的文件。

2b96bb58-dbe2-11ec-ba43-dac502259ad0.png(1) 當(dāng)open函數(shù)打開設(shè)備文件時(shí),可以根據(jù)設(shè)備文件對(duì)應(yīng)的struct inode結(jié)構(gòu)體描述的信息,可以知道接下來要操作的設(shè)備類型(字符設(shè)備還是塊設(shè)備),還會(huì)分配一個(gè)struct file結(jié)構(gòu)體。

(2) 根據(jù)struct inode結(jié)構(gòu)體里面記錄的設(shè)備號(hào),可以找到對(duì)應(yīng)的驅(qū)動(dòng)程序。這里以字符設(shè)備為例。在Linux操作系統(tǒng)中每個(gè)字符設(shè)備都有一個(gè)struct cdev結(jié)構(gòu)體。此結(jié)構(gòu)體描述了字符設(shè)備所有信息,其中最重要的一項(xiàng)就是字符設(shè)備的操作函數(shù)接口。

(3) 找到struct cdev結(jié)構(gòu)體后,linux內(nèi)核就會(huì)將struct cdev結(jié)構(gòu)體所在的內(nèi)存空間首地址記錄在struct inode結(jié)構(gòu)體i_cdev成員中,將struct cdev結(jié)構(gòu)體中的記錄的函數(shù)操作接口地址記錄在struct file結(jié)構(gòu)體的f_ops成員中。

(4) 任務(wù)完成,VFS層會(huì)給應(yīng)用返回一個(gè)文件描述符(fd)。這個(gè)fd是和struct file結(jié)構(gòu)體對(duì)應(yīng)的。接下來上層應(yīng)用程序就可以通過fd找到struct file,然后在struct file找到操作字符設(shè)備的函數(shù)接口file_operation了。

其中,cdev_init和cdev_add在驅(qū)動(dòng)程序的入口函數(shù)中就已經(jīng)被調(diào)用,分別完成字符設(shè)備與file_operation函數(shù)操作接口的綁定,和將字符驅(qū)動(dòng)注冊(cè)到內(nèi)核的工作。

基于框架編寫驅(qū)動(dòng)代碼:

  • 上層調(diào)用代碼:操作驅(qū)動(dòng)的上層代碼(pin4test.c):
#include
#include
#include
#include

voidmain()
{
intfd,data;
fd=open("/dev/pin4",O_RDWR);
if(fd<0){
printf("openfail
");
perror("reson:");
}
else{
printf("opensuccessful
");
}
fd=write(fd,'1',1);
}

-內(nèi)核驅(qū)動(dòng)**==最簡單的字符設(shè)備驅(qū)動(dòng)框架==**:

字符設(shè)備驅(qū)動(dòng)框架代碼

#include//file_operations聲明
#include//module_initmodule_exit聲明
#include//__init__exit宏定義聲明
#include//classdevise聲明
#include//copy_from_user的頭文件
#include//設(shè)備號(hào)dev_t類型聲明
#include//ioremapiounmap的頭文件

staticstructclass*pin4_class;
staticstructdevice*pin4_class_dev;

staticdev_tdevno;//設(shè)備號(hào),devno是用來接收創(chuàng)建設(shè)備號(hào)函數(shù)的返回值,銷毀的時(shí)候需要傳這個(gè)參數(shù)
staticintmajor=231;//主設(shè)備號(hào)
staticintminor=0;//次設(shè)備號(hào)
staticchar*module_name="pin4";//模塊名

//led_open函數(shù)
staticintpin4_open(structinode*inode,structfile*file)
{
printk("pin4_open
");//內(nèi)核的打印函數(shù)和printf類似
return0;
}

//led_write函數(shù)
staticssize_tpin4_write(structfile*file,constchar__user*buf,size_tcount,loff_t*ppos)
{

printk("pin4_write
");//內(nèi)核的打印函數(shù)和printf類似
return0;
}
//將上面的函數(shù)賦值給一個(gè)結(jié)構(gòu)體中,方便下面加載到到驅(qū)動(dòng)鏈表中去
staticstructfile_operationspin4_fops={
//static防止其他文件也有同名pin4_fops
//static限定這個(gè)結(jié)構(gòu)體的作用,僅僅只在這個(gè)文件。
.owner=THIS_MODULE,
.open=pin4_open,
.write=pin4_write,
};
/*
上面的代碼等同于以下代碼(但是在單片機(jī)keil的編譯環(huán)境里面不允許以上寫法):
里面的每個(gè)pin4_fops結(jié)構(gòu)體成員單獨(dú)賦值
staticstructfile_operationspin4_fops;
pin4_fops.owner=THIS_MODULE;
pin4_fops.open=pin4_open;
pin4_fops.write=pin4_write;
*/
//static限定這個(gè)結(jié)構(gòu)體的作用,僅僅只在這個(gè)文件。


int__initpin4_drv_init(void)//真實(shí)的驅(qū)動(dòng)入口
{

intret;
devno=MKDEV(major,minor);//2.創(chuàng)建設(shè)備號(hào)
ret=register_chrdev(major,module_name,&pin4_fops);
//3.注冊(cè)驅(qū)動(dòng)告訴內(nèi)核,把這個(gè)驅(qū)動(dòng)加入到內(nèi)核驅(qū)動(dòng)的鏈表中

pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代碼在dev下自動(dòng)生成設(shè)備,創(chuàng)建一個(gè)類
pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);
//創(chuàng)建設(shè)備文件,先有上面那一行代碼,創(chuàng)建一個(gè)類然后這行代碼,類下面再創(chuàng)建一個(gè)設(shè)備。


return0;
}

void__exitpin4_drv_exit(void)
{

device_destroy(pin4_class,devno);//先銷毀設(shè)備
class_destroy(pin4_class);//再銷毀類
unregister_chrdev(major,module_name);//卸載驅(qū)動(dòng)

}

module_init(pin4_drv_init);//入口,內(nèi)核加載驅(qū)動(dòng)的時(shí)候,這個(gè)宏(不是函數(shù))會(huì)被調(diào)用,去調(diào)用pin4_drv_init這個(gè)函數(shù)
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPLv2");

手動(dòng)創(chuàng)建設(shè)備名

  • 上面這個(gè)字符設(shè)備驅(qū)動(dòng)代碼里面有讓代碼自動(dòng)的在dev下面生成設(shè)備除此之外我們還可以手動(dòng)創(chuàng)建設(shè)備名。使用指令:sudo mknod +設(shè)備名字 +設(shè)備類型(c表示字符設(shè)備驅(qū)動(dòng)) +主設(shè)備號(hào)+次設(shè)備號(hào) b :create a block(buffered) pecial file。c, u:create a character (unbuffered)special file。 p:create a FIFO, 刪除手動(dòng)創(chuàng)建的設(shè)備名直接rm就好。如下圖所示:2bdfaa5c-dbe2-11ec-ba43-dac502259ad0.png

驅(qū)動(dòng)框架執(zhí)行流程:

  • 通過上層程序打開某個(gè)設(shè)備,如果沒有驅(qū)動(dòng),執(zhí)行就會(huì)報(bào)錯(cuò),在內(nèi)核驅(qū)動(dòng)中,上層系統(tǒng)調(diào)用open,wirte函數(shù)會(huì)觸發(fā)sys_call、sys_call會(huì)調(diào)用sys_open,sys_write、sys_open,和sys_write通過主設(shè)備號(hào)在內(nèi)核的驅(qū)動(dòng)鏈表里把設(shè)備驅(qū)動(dòng)找出來,執(zhí)行里面的open和write、我們?yōu)榱苏麄€(gè)流程順利進(jìn)行,我們要先準(zhǔn)備好驅(qū)動(dòng)(設(shè)備驅(qū)動(dòng)文件)。
  • 設(shè)備驅(qū)動(dòng)文件有固定框架:
  1. module_init(pin4_drv_init); //入口 去調(diào)用 pin4_drv_init函數(shù)
  2. int __init pin4_drv_init(void) //真實(shí)的驅(qū)動(dòng)入口
  3. 驅(qū)動(dòng)入口devno = MKDEV(major,minor); // 創(chuàng)建設(shè)備號(hào)
  4. register_chrdev(major, module_name,&pin4_fops); //注冊(cè)驅(qū)動(dòng) 告訴內(nèi)核,把上面準(zhǔn)備好的結(jié)構(gòu)體加入到內(nèi)核驅(qū)動(dòng)的鏈表中
  5. pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代碼在dev下自動(dòng)生成設(shè)備,創(chuàng)建一個(gè)類
  6. pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //創(chuàng)建設(shè)備文件。
  7. 主要是要讓/dev下多了個(gè)文件供我們上層可以open
  8. 如果沒有,也可以手動(dòng)sudo mknod +設(shè)備名字 +設(shè)備類型(c表示字符設(shè)備驅(qū)動(dòng)) +主設(shè)備號(hào)+次設(shè)備號(hào)的去創(chuàng)造設(shè)備

驅(qū)動(dòng)模塊代碼編譯

驅(qū)動(dòng)模塊代碼編譯

驅(qū)動(dòng)模塊代碼編譯(模塊的編譯需要配置過的內(nèi)核源碼,編譯、連接后生成的內(nèi)核模塊后綴為.ko,編譯過程首先會(huì)到內(nèi)核源碼目錄下,讀取頂層的Makefile文件,然后再返回模塊源碼所在目錄。):

  • 使用下面的的代碼:(就是上面的驅(qū)動(dòng)架構(gòu)代碼)
#include//file_operations聲明
#include//module_initmodule_exit聲明
#include//__init__exit宏定義聲明
#include//classdevise聲明
#include//copy_from_user的頭文件
#include//設(shè)備號(hào)dev_t類型聲明
#include//ioremapiounmap的頭文件


staticstructclass*pin4_class;
staticstructdevice*pin4_class_dev;

staticdev_tdevno;//設(shè)備號(hào)
staticintmajor=231;//主設(shè)備號(hào)
staticintminor=0;//次設(shè)備號(hào)
staticchar*module_name="pin4";//模塊名

//led_open函數(shù)
staticintpin4_open(structinode*inode,structfile*file)
{
printk("pin4_open
");//內(nèi)核的打印函數(shù)和printf類似

return0;
}
//read函數(shù)
staticintpin4_read(structfile*file,char__user*buf,size_tcount,loff_t*ppos)
{
printk("pin4_read
");//內(nèi)核的打印函數(shù)和printf類似

return0;
}

//led_write函數(shù)
staticssize_tpin4_write(structfile*file,constchar__user*buf,size_tcount,loff_t*ppos)
{

printk("pin4_write
");//內(nèi)核的打印函數(shù)和printf類似
return0;
}

staticstructfile_operationspin4_fops={

.owner=THIS_MODULE,
.open=pin4_open,
.write=pin4_write,
.read=pin4_read,
};
//static限定這個(gè)結(jié)構(gòu)體的作用,僅僅只在這個(gè)文件。
int__initpin4_drv_init(void)//真實(shí)的驅(qū)動(dòng)入口
{

intret;
devno=MKDEV(major,minor);//創(chuàng)建設(shè)備號(hào)
ret=register_chrdev(major,module_name,&pin4_fops);//注冊(cè)驅(qū)動(dòng)告訴內(nèi)核,把這個(gè)驅(qū)動(dòng)加入到內(nèi)核驅(qū)動(dòng)的鏈表中

pin4_class=class_create(THIS_MODULE,"myfirstdemo");//讓代碼在dev下自動(dòng)>生成設(shè)備
pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//創(chuàng)建設(shè)備文件


return0;
}

void__exitpin4_drv_exit(void)
{

device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major,module_name);//卸載驅(qū)動(dòng)
}
module_init(pin4_drv_init);//入口,內(nèi)核加載驅(qū)動(dòng)的時(shí)候,這個(gè)宏會(huì)被調(diào)用,去調(diào)用pin4_drv_init這個(gè)函數(shù)
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPLv2");
  • 在導(dǎo)入虛擬機(jī)的內(nèi)核代碼中找到字符設(shè)備驅(qū)動(dòng)的那一個(gè)文件夾:/SYSTEM/linux-rpi-4.19.y/drivers/char將以上代碼復(fù)制到一個(gè)文件中,然后下一步要做的是就是:將上面的驅(qū)動(dòng)代碼編譯生成模塊,再修改Makefile。(你放那個(gè)文件下,就改哪個(gè)文件下的Makefile)
  • 文件內(nèi)容如下圖所示:(-y表示編譯進(jìn)內(nèi)核,-m表示生成驅(qū)動(dòng)模塊,CONFIG_表示是根據(jù)config生成的)所以只需要將obj-m += pin4drive.o添加到Makefile中即可。下圖:Makefile文件圖2c4cefc2-dbe2-11ec-ba43-dac502259ad0.png
  • 編譯生成驅(qū)動(dòng)模塊,將生成的.ko文件發(fā)送給樹莓派然后回/SYSTEM/linux-rpi-4.19.y下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules進(jìn)行編譯生成驅(qū)動(dòng)模塊。然后將生成的.ko文件發(fā)送給樹莓派:scp drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi編譯生成驅(qū)動(dòng)模塊會(huì)生成以下幾個(gè)文件:2ca84eee-dbe2-11ec-ba43-dac502259ad0.png
  • .o的文件是object文件,.ko是kernel object,與.o的區(qū)別在于其多了一些sections,比如.modinfo。.modinfo section是由kernel source里的modpost工具生成的,包括MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, device ID table以及模塊依賴關(guān)系等等。depmod 工具根據(jù).modinfo section生成modules.dep, modules.*map等文件,以便modprobe更方便的加載模塊。
  • 編譯過程中,經(jīng)歷了這樣的步驟
  1. 先進(jìn)入Linux內(nèi)核所在的目錄,并編譯出pin4drive.o文件
  2. 運(yùn)行MODPOST會(huì)生成臨時(shí)的pin4drive.mod.c文件, 而后根據(jù)此文件編譯出pin4drive.mod.o,
  3. 之后連接pin4drive.o和pin4drive.mod.o文件得到模塊目標(biāo)文件pin4drive.ko,
  4. 最后離開Linux內(nèi)核所在的目錄。

pin4test.c (上層調(diào)用代碼) 進(jìn)行 交叉編譯后發(fā)送給樹莓派,就可以看到pi目錄下存在發(fā)送過來的.ko文件pin4test這兩個(gè)文件,如下圖所示:2cdfd184-dbe2-11ec-ba43-dac502259ad0.png

加載內(nèi)核驅(qū)動(dòng)

然后使用指令:sudo insmod pin4drive.ko加載內(nèi)核驅(qū)動(dòng)(相當(dāng)于通過insmod調(diào)用了module_init這個(gè)宏,然后將整個(gè)結(jié)構(gòu)體加載到驅(qū)動(dòng)鏈表中)加載完成后就可以在dev下面看到名字為pin4的設(shè)備驅(qū)動(dòng)(這個(gè)和驅(qū)動(dòng)代碼里面static char *module_name="pin4"; //模塊名這行代碼有關(guān)),設(shè)備號(hào)也和代碼里面相關(guān)。

2d02414c-dbe2-11ec-ba43-dac502259ad0.pnglsmod可以查看驅(qū)動(dòng)已經(jīng)裝進(jìn)去了。2d221e36-dbe2-11ec-ba43-dac502259ad0.png

  • 我們?cè)賵?zhí)行./pin4test 執(zhí)行上層代碼 執(zhí)行上層代碼出現(xiàn)以下錯(cuò)誤:表示沒有權(quán)限 2d4960fe-dbe2-11ec-ba43-dac502259ad0.png使用指令:sudo chmod 666 /dev/pin4為pin4賦予權(quán)限,讓所有人都可以打開成功。

然后再次執(zhí)行pin4test表面上看沒有任何信息輸出,其實(shí)內(nèi)核里面有打印信息只是上層看不到如果想要查看內(nèi)核打印的信息可以使用指令:dmesg |grep pin4。如下圖所示:表示驅(qū)動(dòng)調(diào)用成功2d6e4d4c-dbe2-11ec-ba43-dac502259ad0.png在裝完驅(qū)動(dòng)后可以使用指令:sudo rmmod +驅(qū)動(dòng)名(不需要寫ko)將驅(qū)動(dòng)卸載。

為什么生成驅(qū)動(dòng)模塊需要在虛擬機(jī)上生成

  • 為什么生成驅(qū)動(dòng)模塊需要在虛擬機(jī)上生成?樹莓派不行嗎?

    生成驅(qū)動(dòng)模塊需要編譯環(huán)境(linux源碼并且編譯,需要下載和系統(tǒng)版本相同的Linux內(nèi)核源代碼),也可以在樹莓派上面編譯,但在樹莓派里編譯,效率會(huì)很低,要非常久。這篇文章有講樹莓派驅(qū)動(dòng)的本地編譯。

原文標(biāo)題:Linux中級(jí)——“驅(qū)動(dòng)” 控制硬件必須學(xué)會(huì)的底層知識(shí)

文章出處:【微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式系統(tǒng)
    +關(guān)注

    關(guān)注

    40

    文章

    3520

    瀏覽量

    128805
  • 驅(qū)動(dòng)
    +關(guān)注

    關(guān)注

    12

    文章

    1790

    瀏覽量

    84906
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11123

    瀏覽量

    207900

原文標(biāo)題:Linux中級(jí)——“驅(qū)動(dòng)” 控制硬件必須學(xué)會(huì)的底層知識(shí)

文章出處:【微信號(hào):yikoulinux,微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語言基礎(chǔ)知識(shí)科普

    C語言是單片機(jī)開發(fā)中的必備基礎(chǔ)知識(shí),本文列舉了部分STM32學(xué)習(xí)中比較常見的一些C語言基礎(chǔ)知識(shí)。
    發(fā)表于 07-21 10:58 ?1815次閱讀

    Linux基礎(chǔ)知識(shí)和命令

    Linux基礎(chǔ)知識(shí)和命令
    發(fā)表于 06-14 06:39

    嵌入式linux應(yīng)用開發(fā)基礎(chǔ)知識(shí)

    :嵌入式linux應(yīng)用開發(fā)基礎(chǔ)知識(shí) BV1kk4y117Tu第5篇:嵌入式linux驅(qū)動(dòng)開發(fā)基礎(chǔ)知識(shí) BV14f4y1Q7ti第6篇:項(xiàng)目實(shí)
    發(fā)表于 12-24 08:18

    Linux基礎(chǔ)知識(shí)

    Linux基礎(chǔ)知識(shí) 硬盤 硬盤是可以存儲(chǔ)大量信息資源的媒介。我們平時(shí)看到的硬盤是方方正正的一塊挺沉的鐵匣子,但是其實(shí)硬盤是圓的,加上一些控制電路以后,為了便于
    發(fā)表于 01-18 09:57 ?475次閱讀

    linux+Android基礎(chǔ)知識(shí)總結(jié)

    linux+Android基礎(chǔ)知識(shí)總結(jié)
    發(fā)表于 03-19 11:23 ?0次下載

    linux /Android 基礎(chǔ)知識(shí)總結(jié)大全

    本文檔介紹了linux /Android 基礎(chǔ)知識(shí)總結(jié)大全,包含了源代碼以及詳解,供網(wǎng)友參考。
    發(fā)表于 09-11 17:46 ?7次下載

    linux Android基礎(chǔ)知識(shí)總結(jié)

    linux Android基礎(chǔ)知識(shí)總結(jié)
    發(fā)表于 10-24 09:00 ?6次下載
    <b class='flag-5'>linux</b> Android<b class='flag-5'>基礎(chǔ)知識(shí)</b>總結(jié)

    嵌入式Linux設(shè)備驅(qū)動(dòng)程序開發(fā)基礎(chǔ)知識(shí)總結(jié)免費(fèi)下載

    本文檔的主要內(nèi)容詳細(xì)介紹的是嵌入式Linux設(shè)備驅(qū)動(dòng)程序開發(fā)基礎(chǔ)知識(shí)總結(jié)免費(fèi)下載 嵌入式Linux設(shè)備驅(qū)動(dòng)程序分類靜態(tài)加載的
    發(fā)表于 10-23 16:10 ?13次下載

    Linux設(shè)備驅(qū)動(dòng)程序基礎(chǔ)知識(shí)的了解

    了解Linux設(shè)備驅(qū)動(dòng)程序的基礎(chǔ)知識(shí),重點(diǎn)關(guān)注設(shè)備節(jié)點(diǎn),內(nèi)核框架,虛擬文件??系統(tǒng)和內(nèi)核模塊。 提出了一個(gè)簡單的內(nèi)核模塊實(shí)現(xiàn)。
    的頭像 發(fā)表于 11-26 06:51 ?3028次閱讀

    Linux驅(qū)動(dòng)編程基礎(chǔ)知識(shí)講解

    由于Linux驅(qū)動(dòng)編程的本質(zhì)屬于Linux內(nèi)核編程,因此我們非常有必要熟悉Linux內(nèi)核以及Linux內(nèi)核的特點(diǎn)。 這篇文章將會(huì)幫助讀者打下
    的頭像 發(fā)表于 03-01 08:27 ?3749次閱讀

    linux操作系統(tǒng)基礎(chǔ)知識(shí)

    本文主要闡述了linux操作系統(tǒng)基礎(chǔ)知識(shí)。
    發(fā)表于 06-04 15:07 ?5835次閱讀

    Linux用戶態(tài)開發(fā)驅(qū)動(dòng)教程及基礎(chǔ)知識(shí)

    Linux用戶態(tài)開發(fā)驅(qū)動(dòng)教程及基礎(chǔ)知識(shí)
    發(fā)表于 07-14 10:06 ?7次下載

    Windows驅(qū)動(dòng)類型及基礎(chǔ)知識(shí)

    Windows驅(qū)動(dòng)類型及基礎(chǔ)知識(shí)
    發(fā)表于 07-14 10:02 ?14次下載

    伺服驅(qū)動(dòng)基礎(chǔ)知識(shí)_pdf

    伺服驅(qū)動(dòng)基礎(chǔ)知識(shí)_pdf
    發(fā)表于 12-13 10:25 ?0次下載

    Linux內(nèi)存管理的基礎(chǔ)知識(shí)科普

    Linux的內(nèi)存管理可謂是學(xué)好Linux的必經(jīng)之路,也是Linux的關(guān)鍵知識(shí)點(diǎn),有人說打通了內(nèi)存管理的知識(shí),也就打通了
    的頭像 發(fā)表于 06-08 15:24 ?1990次閱讀