目錄
- 1. 字符設備驅動簡介
- 1.1 重要函數
- 1.2 簡單框架代碼流程
- 1.3 linux中關于驅動的重要命令
- 2. 字符設備驅動簡單框架編寫
- 2.1 添加LICENSE信息
- 2.2 驅動模塊的入口與出口
- 2.3 入口和出口函數的編寫
- 2.4 設備操作結構體定義
- 2.4.1 結構體函數內容填充
- 3. 應用程序簡介:
- 4. 應用程序的編寫思路
1. 字符設備驅動簡介
??目前的驅動開發一般是分為三類,第一類就是字符設備驅動、塊設備驅動、和網絡驅動三類,其中字符設備驅動是最多最雜的,現在對字符設備驅動進行一個簡要的介紹:
??字符設備驅動,是指那些以字節流進行數據傳輸的設備、IIC、SPI、LCD、按鍵、例如鍵盤、鼠標、打印機等,其中包含以下幾個關鍵的部分:
設備注冊和注銷:
通過設備注冊使設備能被系統識別;注銷則相反;數據操作函數:
通常包含open,read,write,realse等;中斷處理:
處理設備產生的中斷,以響應特定事件;
?? 通過字符型設備驅動,可以使系統方便統一管理不同的設備,這樣就可以給上層應用提供相應的接口函數,方便應用程序與設備之間進行數據交換和通信;
?? 本次的實驗是通過簡單的實驗建立一個設備驅動開發的基本框架,為后續的學習打下基礎
,其中會列出重要的函數以及重要的掛載指令,當然其中有一些函數是比較老的,例如要手動分配設備號,但是為了便于學習目前就以簡單的為主
,因為越是抽象的越簡單,但是越抽象就越難以理解;
1.1 重要函數
??這里只是把本實驗相關的重要函數給羅列出來了,主要的作用就是對本次實驗的一個總結,如果沒有做個這個實驗,那么看著沒啥感覺的;例如我下面羅列函數的順序就是我們在編寫驅動實驗時整個流程的順序,首先就是注冊入口和出口函數,其次就是編寫入口和出口函數,再次就是編寫文件結構體的對應相關的函數內容;
MODULE-LICENSE("GPL")
: 表示該內核模塊遵循的許可協議是通用公共許可(GPL)。指定許可協議非常重要,它明確了該模塊在使用、分發等方面的權利和限制。如果不寫這個的話就會導致在裝載設備驅動時出現警告;module_init(*****_init)
:模塊的入口函數,進行初始化,也就是加載模塊時第一個就是運行這個函數注冊的函數*******;對模塊進行初始化設置;module_exit(*****_exit)
:模塊的出口函數,對模塊進行卸載時就會執行這個函數注冊的函數*******;static int __init *****_init(void)
:設備加載函數,進行模塊的初始化和加載的設置;這個函數要被入口函數進行注冊后起作用
;register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
:對設備進行注冊;static int __exit *****_exit(void)
:設備卸載函數、進行模塊的卸載時這個函數內部的程序就會執行,不過要被出口函數進行注冊
;unregister_chrdev(unsigned int major, const char *name)
:對模塊進行卸載;static const struct file_operations **_fops
: struct file_operations是一個很重要的結構體,其中定義了很多與文件操作有關的函數指針,例如read,write,realse,open等等,這些函數可以按需進行填充,這樣就方便與設備之間進行文件的操作;下面進行實驗時會進行一個詳細的說明;
1.2 簡單框架代碼流程
??代碼實驗編寫流程:
1.3 linux中關于驅動的重要命令
lsmod
:顯示有哪些模塊被加載了,也就是顯示所有的加載模塊lsmod
:顯示有哪些模塊被加載了,也就是顯示所有的加載模塊depmod
:更新模塊的依賴關系,也就是新加載一個模塊時,要先運行一下這個命令,不然有錯誤modprob ***.ko
:這個命令的作用就是加載模塊***.komknod /dev/*** c 200 0
:mknod:手動創建節點的命令。/dev/*** :創建設備的名稱。c:字符型設備。200:主設備號,自己指定。0:次設備號,自己指定。cat /proc/devices
:這個命令的作用是查看所有加載的模塊,顯示所有設備號等;rmmod ***.ko
:卸載模塊***.ko;cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
:這個命令是顯示當成CPU的頻率;
2. 字符設備驅動簡單框架編寫
??上面已經介紹了驅動編寫的則整體流程,以及介紹了重要的加載驅動的一些linux的命令,因此下面的代碼就不做過多的介紹:
2.1 添加LICENSE信息
??我們需要在代碼中添加LICENSE信息,否則編譯會出錯,不過我們還可以添加一些其他的信息,例如作者,郵箱等等;
MODULE_LICENSE("GPL");
2.2 驅動模塊的入口與出口
??linux的驅動有兩種加載形式,一就是編譯進linux內核中,當內核啟動,驅動也啟動,另一種方法就是把啟動編譯成模塊也就***.ko文件,通過insmode或者modprob的命令進行加載模塊,這樣做的好處就是便于調試,而且不用重啟linux內核;代碼如下,下面兩個函數的作用就是對模塊進行加載和卸載,并在加載和卸載函數中對字符設備進行注冊,如我們裸機編程中系統中斷函數對中斷服務函數的注冊一樣;
/* * 模塊入口和出口函數注冊*/
module_init(chrdevbase_init);/*入口,加載模塊*/
module_exit(chrdevbase_exit);/*出口,卸載模塊*/
2.3 入口和出口函數的編寫
??注意,入口函數是通過__init
來修飾(注意是兩個杠),而對于出口函數通過__exit
進行修飾,在出入口函數中對設備進行注冊,CHRDEVBASE_MAJOR是設備號,CHREEVBAScE_NAME是設備名,chrdevbase_fops是文件操作結構體;出口函數是類似的;
#define CHRDEVBASE_MAJOR 200
#define CHREEVBASE_NAME "chrdevbase"static int __init chrdevbase_init(void)
{printk("chrdevbase_init3\r\n");/*注冊字符設備*/register_chrdev(CHRDEVBASE_MAJOR, CHREEVBASE_NAME,&chrdevbase_fops);return 0;
}
static void __exit chrdevbase_exit(void)
{printk("chrdevbase_exit\r\n");unregister_chrdev(CHRDEVBASE_MAJOR,CHREEVBASE_NAME);
}
2.4 設備操作結構體定義
??對于設備的操作file_operations
結構體,也稱為文件操作結構體,這也是為什么linux下一切皆文件,我們的操作大部分都是通過文件來進行管理,我們可能用不到那么多的功能,因此我們要用到什么功能就進行對應的添加和書寫就行,代碼如下,注意這里使用的是=:
static const struct file_operations chrdevbase_fops={.owner = THIS_MODULE,.open = chrdevbase_open,.release = chrdevbase_release,.read = chrdevbase_read,.write = chrdevbase_write,
};
2.4.1 結構體函數內容填充
??可以看到,我們對上面的文件操作結構體中定義了四個函數指針,分別是:chrdevbase_open、chrdevbase_release、chrdevbase_read、chrdevbase_write
,接下來就是對這四個函數進行內容填充:
static char readbuf[100];/*讀緩沖*/
static char writebuf[100];
static char kerneldata[]={"kernel data!"};/************/
static ssize_t chrdevbase_read(struct file * file, char * buf, size_t count, loff_t *off)
{int ret=0;memcpy(readbuf,kerneldata,sizeof(kerneldata));ret = copy_to_user(buf,readbuf,count);if(ret<0){printk("Error!");}else{}return 0;
}
/************/
static ssize_t chrdevbase_write(struct file * file, const char * buf, size_t count,loff_t *off)
{int ret =0;ret = copy_from_user(writebuf,buf,count);printk("Kernel recevdata:%s\r\n",writebuf);if(ret = 0){printk("Kernel recevdata:%s\r\n",writebuf);}return 0;
}
static int chrdevbase_release(struct inode * inode, struct file * file)
{printk("chrdevbase_ralease\r\n");return 0;
}
static int chrdevbase_open(struct inode * inode, struct file * file)
{printk("chrdevbase_open\n");return 0;
}
3. 應用程序簡介:
??當驅動程序編寫完畢后,要通過應用程序進行調用驅動函數的一些接口函數,這些功能的實現是在應用程序中實現的,這樣就實現了應用程序與驅動程序的分離
;可以這樣類比,我們寫應用程序就相當于我們在windows系統上寫C程序,我們寫C程序時也沒有關注下層,這就是一種分離
,不過這兩者是可以相互進行數據交換的,不過要用特定的方法;注意驅動程序和應用程序的編譯是不一樣的:對于應用程序的編譯是下面的指令:
arm-linux-gnueabihf-gcc ***APP.c -o ***APP
:應用程序的編譯指令$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
:驅動程序的編譯:Make部分核心指令
?? 從上面的編譯指令中就可以看出區別,對于驅動的編譯要用到一系列的庫,這些庫包含板子的信息,以及一些arm中的一些庫等等,最終生成一個驅動模塊,屬于下層的驅動文件;
??而對于應用程序的編譯則是用到了一個.c文件以及基礎庫,就像我們編譯C語言一樣,只不過我們寫一個hello.c文件用的是gcc編譯器,生成的是x86架構的可執行文件,同理,我們使用arm-linux-gnueabihf-gcc交叉編譯器生成的是arm架構的可執行文件,這明顯是上層的應用程序,不過我們寫上層應用程序時通過傳參命令的形式就可以與下層的驅動進行數據文件交換;
4. 應用程序的編寫思路
??編寫測試APP就是編寫Linux應用程序,需要用到C庫和文件操作相關的一些函數,open、read、write 和 close 這四個函數;這些函數可以根據下面的命令進行找詳細幫助:“man 1”通常是用戶命令的手冊頁;“man 2”一般是系統調用的手冊頁;“man 3”可能是 C 庫函數的手冊頁;
wyj@BK:~$ man 2 read
wyj@BK:~$ man 2 write
wyj@BK:~$ man 2 close
wyj@BK:~$ man 2 open
??因此在應用程序中編寫程序就是如何對驅動程序進行調用和使用,不過要注意的是對于驅動的編寫屬于內核態,而對于應用程序的編寫屬于應用態,應用態不能直接操作內核態,要通過一定的程序從而間接操作內核態
:
int mian(int argc,char *argv[])
{int ret = 0;int fd = 0;char *filename;char readbuf[100];char writebuf[100];static char usrdata[]={"Usr data!Usr data!Usr data!"};filename=argv[1];fd = open(filename, O_RDWR);if(fd<0){printf("Can't open file %s\r\n",filename);}/*read*/if(atoi(argv[2])==1){ret=read(fd, readbuf, 50); printf("\r\nAPP read data:%s\r\n",readbuf);}/*write*/if(atoi(argv[2])==2){memcpy(writebuf,usrdata,sizeof(usrdata));ret = write(fd, writebuf,50);}/*close*/ret = close(fd);if(ret<0){printf("Can't close %s\r\n",filename);}return 0;
}