第零章:扯扯淡
特此總結(jié)一下寫的一個簡單字符設(shè)備驅(qū)動程序的過程,我要強調(diào)一下“自頂向下”這個介紹方法,因為我覺得這樣更容易讓沒有接觸過設(shè)備驅(qū)動程序的童鞋更容易理解,“自頂向下”最初從《計算機網(wǎng)絡(luò) 自頂向下方法》這本書學(xué)到的,我覺得有時候這是一種很好的方式。
第一章:測試程序
咦?你怎么跟別人的思路不一樣???自頂向下嘛,我就直接從測試程序來說啦,這樣那個不是更熟悉嗎?看看下面的測試程序的代碼,是不是很熟悉?

1 #include <stdio.h> 2 #include <unistd.h> 3 #include <fcntl.h> 4 5 #define MY_CDEV_NAME "/dev/mychardev" 6 #define BUF_LEN 16 7 8 int main(void) 9 {10 int fd;11 int ret,i;12 char buf[BUF_LEN];13 14 /*打開設(shè)備*/15 fd=open(MY_CDEV_NAME,O_RDWR | O_NONBLOCK);16 if(fd<0)17 {18 PRintf("open %s fail!/n",MY_CDEV_NAME);19 return -1;20 }21 printf("open %s success!/n",MY_CDEV_NAME);22 23 /*設(shè)置buf的數(shù)據(jù),以后會寫進設(shè)備*/24 for(i=0;i<BUF_LEN;i++)25 {26 buf[i]=i+65;27 }28 29 30 /*寫設(shè)備*/31 if((ret=write(fd,buf,BUF_LEN))<0)32 {33 printf("write %s fail!/n",MY_CDEV_NAME);34 }35 else36 {37 printf("write %s success! Write totoal:%d/n",MY_CDEV_NAME,ret);38 }39 40 /*把文件偏移量設(shè)置為文件開始處*/41 if((ret=lseek(fd,0,SEEK_SET))<0)42 {43 printf("lseek %s fail!/n",MY_CDEV_NAME);44 }45 else46 {47 printf("lseek %s success! Now position:%d/n",MY_CDEV_NAME,ret);48 }49 50 /*讀設(shè)備*/51 if((ret=read(fd,buf+BUF_LEN/2,BUF_LEN))<0)52 {53 printf("read %s fail!/n",MY_CDEV_NAME);54 }55 else56 {57 printf("read %s success! Read totoal:%d/n",MY_CDEV_NAME,ret);58 }59 60 for(i=0;i<BUF_LEN;i++)61 {62 printf("buf[%d]:%c/n",i,buf[i]);63 }64 65 close(fd);66 67 return 0; 68 }最終測試代碼這里其實不就是Unix環(huán)境打開一個普通的文件嘛,我打開的是/dev/mychardev ,雖然是一個字符設(shè)備文件,但是操作系統(tǒng)統(tǒng)一了接口,所以和打開一個普通文件沒有區(qū)別,先open(),再向文件write(),再把當前文件偏移量設(shè)置為文件的開始處lseek(),再讀讀看read(),最后關(guān)閉close()。寫寫這些代碼越發(fā)覺得Linux真是厲害,基本上把所有的設(shè)備都當做文件給處理掉了,系統(tǒng)提供統(tǒng)一的接口給上層,酷斃了!
到這里基本上沒有問題,唯一的問題是/dev/mychardev 這個文件怎么來的???
第二章:設(shè)備文件怎么來的
第0節(jié)、Linux文件類型簡單介紹:
linux系統(tǒng)將設(shè)備基本分為3類:字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備。詳情請搜搜,我不會告訴你我理解不夠深刻……
第1節(jié)、先看看這個文件屬性:

看最前面是“c”,說明是一個字符設(shè)備文件。我不會告訴你這個文件是我自己創(chuàng)建的,O(∩_∩)O哈哈~
第2節(jié)、Linux創(chuàng)建一個字符設(shè)備文件很簡單,只要mknod命令即可:

說得很清楚,創(chuàng)建字符或塊特殊設(shè)備文件。所以要創(chuàng)建一個字符設(shè)備很簡單啊:
sudo mknod /dev/mychardev c 248 0
看,這不就創(chuàng)建了一個字符設(shè)備文件嘛,sudo要獲得權(quán)限;/dev/mychardev 為文件名,其實也指定了文件路徑;c表明創(chuàng)建的是字符設(shè)備文件,塊設(shè)備文件就是b啦;最后重要的是 major 和 minor 這兩個參數(shù),我創(chuàng)建時用248和0替換的,這個不是小打小鬧瞎搞的,有來頭!
第3節(jié)、為什么在/dev目錄:
linux的/dev目錄里,存放的是系統(tǒng)里的各種設(shè)備文件,每個設(shè)備對應(yīng)一個設(shè)備文件,而我們就是通過這個設(shè)備文件訪問和使用這個設(shè)備的,即打開這個文件相當于打開設(shè)備了,向文件里面寫數(shù)據(jù)相當于把數(shù)據(jù)寫到設(shè)備了,讀文件相當于從設(shè)備中讀數(shù)據(jù)了。咱們不是要創(chuàng)建一個字符設(shè)備嘛,雖然不知道這個設(shè)備具體是什么鳥樣,但是總有一個設(shè)備文件來對應(yīng)這個設(shè)備。
第4節(jié)、設(shè)備文件主次設(shè)備號:
從上一節(jié)可以看到,/dev目錄下是各種雜七雜八的設(shè)備文件,這些設(shè)備文件是怎么樣對應(yīng)設(shè)備的呢?它們兩個好基友肯定要一一對應(yīng)嘛。為了管理這些設(shè)備,操作系統(tǒng)為設(shè)備編了號,每個設(shè)備號又分為主設(shè)備號和次設(shè)備號。
從設(shè)備文件與設(shè)備來講:主設(shè)備號用來區(qū)分不同種類的設(shè)備;而次設(shè)備號用來區(qū)分同一類型的多個設(shè)備。
從設(shè)備文件與設(shè)備驅(qū)動來講:主設(shè)備號用來標識與設(shè)備文件相連的驅(qū)動程序,用來反映設(shè)備類型;次設(shè)備號被驅(qū)動程序用來辨別操作的是哪個設(shè)備,用來區(qū)分同類型的設(shè)備。
上面兩個角度意思一樣。舉個例子:假如我的電腦連了2臺打印機,打印機類型完全一樣,那么按照前面講的,在/dev目錄下肯定會有2個設(shè)備文件來對應(yīng)這2個打印機,比如有/dev/printer1和/dev/printer2,對這兩個文件的讀寫其實就是對打印機1和2的讀寫操作。但是由于這兩個打印機類型一樣,所以它們的驅(qū)動程序不也是一樣的嗎?沒必要把2份一樣的驅(qū)動代碼加載到操作系統(tǒng)的內(nèi)核吧?所以就讓這兩個文件的主設(shè)備號一樣就可以了,比如都是248。好了,我們加載了一個驅(qū)動程序模塊到內(nèi)核里面,下面這個驅(qū)動程序怎么知道是向哪個打印機發(fā)送數(shù)據(jù)呢?這時候就是次設(shè)備號起作用了,比如給它們分配1和2次設(shè)備號,這樣不久區(qū)分了嘛。所以我就可以這樣:sudo mknod /dev/printer1 c 248 0 和sudo mknod /dev/printer2 c 248 1。這樣就創(chuàng)建了2設(shè)備文件以對應(yīng)2設(shè)備。
好了,到這里設(shè)備文件是怎么來的解決了:自己創(chuàng)建的唄。那么唯一的問題是主設(shè)備號與次設(shè)備號怎么來的???
第三章:向系統(tǒng)裝載設(shè)備驅(qū)動模塊
這里沒有介紹上一章主設(shè)備號與次設(shè)備號怎么來的問題,因為是程序執(zhí)行過程中從系統(tǒng)獲得的,具體要看代碼,但是在把驅(qū)動程序裝載到系統(tǒng)會執(zhí)行一個函數(shù),一般會在這個函數(shù)里面向系統(tǒng)要設(shè)備號,所以可以在這個函數(shù)里面打印獲得設(shè)備號。所以在把驅(qū)動模塊裝載到系統(tǒng)是可以看到設(shè)備號的,前提是你一定寫了一個打印函數(shù)。
這一章講的是如果你已經(jīng)把具體的設(shè)備驅(qū)動程序?qū)懞昧酥螅銘?yīng)該怎么把它裝載到系統(tǒng)。看下面的文件夾:

我寫的驅(qū)動程序的源文件只有一個:myCDev.c,就這個,還有就是Makefile,另外test.c和a是測試源文件和生成的測試程序。其余的都是執(zhí)行make命令時生成的,也就是編譯生成的文件。其中有個.ko的文件,向系統(tǒng)裝載驅(qū)動和這個密切相關(guān)。
既然我們是自頂向下的,假設(shè)下層已經(jīng)提供了源文件,也已經(jīng)編譯好了,現(xiàn)在向系統(tǒng)裝載驅(qū)動程序只要一個命令:
sudo insmod myCDev.ko

insmod就是干這個事情的。相對應(yīng):
sudo rmmod myCDev
是卸載模塊的,模塊不用了可以直接從內(nèi)核去掉的,注意insmod有.ko后綴,這個沒有。
這里有兩個命令;dmesg和cat /proc/kmsg 可以查看你在源代碼的中printk()函數(shù)的輸出,因為一般會在模塊裝載和卸載時調(diào)用特定的函數(shù)。
好了,這一章解決了如果我們已經(jīng)寫好了驅(qū)動程序并編譯好了,如何裝載的問題。
第四章:正式編寫驅(qū)動程序源代碼文件
第0節(jié)、虛擬字符驅(qū)動設(shè)備大概原理:
如果這個設(shè)備不是虛擬的話,我向它寫數(shù)據(jù)之類的是會寫到這個設(shè)備的,如打印機,向打印機寫數(shù)據(jù)直接通過連線寫到了打印機,打印機再看著辦。但是是虛擬的設(shè)備,我向它寫數(shù)據(jù)寫到哪里呢?要從這個設(shè)備哪里讀數(shù)據(jù)呢?總不能寫到空氣中吧?于是只要在內(nèi)存開辟一塊存儲空間,向這個空間寫的相當于寫到了具體設(shè)備,要從設(shè)備讀數(shù)據(jù)也就只要從這個內(nèi)存讀就可以了。
弄清了這個就好辦了。
第1節(jié)、需要包含的頭文件位置:
寫程序嘛,少不了包含頭文件,也就是別人寫好的東西。linux內(nèi)核基本是用c寫的,所以也是用include的。但是程序是在內(nèi)核態(tài)運行的而非用戶態(tài),所以需要包含的頭文件不一樣了。看:

1 /*包含我的電腦中已有的linux內(nèi)核源代碼,注意版本,我的路徑:/usr/src/linux-headers-3.13.0-30/include*/ 2 #include <linux/cdev.h> 3 #include <linux/module.h> 4 #include <linux/types.h> 5 #include <linux/init.h> 6 #include <linux/errno.h> 7 #include <linux/mm.h> 8 #include <linux/sched.h> 9 #include <linux/init.h>10 #include <linux/fs.h>11 #include <linux/kdev_t.h>12 #include <linux/slab.h>頭文件
這些頭文件按需要包含,我的系統(tǒng)自己就有這個操作系統(tǒng)的源代碼,上面說明了。其實在/usr/src文件夾下還有其他版本的內(nèi)核源代碼,因為我的Ubuntu總是更新到最新。

第2節(jié)、模塊裝載與卸載時會調(diào)用的函數(shù):
一個模塊裝載到內(nèi)核時會執(zhí)行一個函數(shù)的,這個函數(shù)是你自己寫的,然后告訴系統(tǒng)一下就行了,就是說這樣:嗨,系統(tǒng)兄弟,把我這個模塊加載到內(nèi)核時執(zhí)行的函數(shù)是這個哦,記住哈。同樣把模塊從內(nèi)核卸載也會調(diào)用一個特定函數(shù),你只要寫好告訴它一下就好了。函數(shù)原型如下:
int my_init(void);void my_exit(void);
所以你可以自己寫自己希望驅(qū)動程序被加載到內(nèi)核和從中卸載時執(zhí)行的函數(shù),我的如下:

1 /*初始化函數(shù),當模塊裝載時被調(diào)用,如果裝載成功返回0,否則返回非0值*/ 2 static int myCDevInit(void) //int my_init(void); 3 { 4 int res; //初始化函數(shù)的返回值 5 6 printk(KERN_EMERG "/n/n/nmyCDevInit() process.../n"); 7 8 /*動態(tài)分配設(shè)備號*/ 9 res=alloc_chrdev_region(&myDev,0,1,"MyCharDev");10 if(res<0) //表示分配設(shè)備號失敗11 {12 return res;13 }14 printk(KERN_EMERG "myCDevInit(): alloc_chrdev_region() success! major:%d,minor:%d/n", MAJOR(myDev), MINOR(myDev)); //打印獲得的主次設(shè)備號15 16 /*為設(shè)備描述結(jié)構(gòu)分配內(nèi)存*/17 pMyCharDev= kmalloc(sizeof(struct MyCharDev), GFP_KERNEL); 18 if(!pMyCharDev)19 {20 res=-ENOMEM; //系統(tǒng)定義的內(nèi)存不足21 goto failMalloc;22 } 23 memset(pMyCharDev,0,sizeof(struct MyCharDev));24 printk(KERN_EMERG "myCDevInit(): kmalloc() success!/n");25 26 /*下面初始化及注冊字符設(shè)備到系統(tǒng)中*/27 cdev_init(&(pMyCharDev->myCDev),&myCDevOps); //初始化struct cdev結(jié)構(gòu)28 cdev_add(&(pMyCharDev->myCDev),myDev,1); //注冊字符設(shè)備29 printk(KERN_EMERG "myCDevInit(): cdev_init() and cdev_add() success!/nmyCDevInit() process success!/n");30 31 return 0; //一切正常返回032 33 failMalloc: //內(nèi)存分配不足時跳到這里34 unregister_chrdev_region(myDev,1);35 return res;36 }模塊初始化函數(shù)
/*退出函數(shù),當模塊從內(nèi)存卸載時被調(diào)用*/static void myCDevExit(void) //void my_exit(void);{ printk(KERN_EMERG "myCDevExit() process.../n"); cdev_del(&(pMyCharDev->myCDev)); //注銷設(shè)備 kfree(pMyCharDev); //釋放設(shè)備結(jié)構(gòu)體內(nèi)存 unregister_chrdev_region(myDev,1); //釋放設(shè)備號 printk(KERN_EMERG "myCDevExit() process success!/n"); }模塊卸載時調(diào)用具體在函數(shù)里面干啥了再說,看不懂沒關(guān)系。怎么告訴系統(tǒng)我的這模塊裝載和卸載時是調(diào)用這兩個呢?通過下面的宏:
1 module_init(myCDevInit); //通過module_init例程把模塊入口點myCDevInit注冊到系統(tǒng)中2 module_exit(myCDevExit); //由module_exit例程把模塊出口函數(shù)注冊到系統(tǒng)
這樣就告訴操作系統(tǒng)了,這兩個宏也是在某個源代碼文件定義的,我還沒找到……
下面的一些宏也是告訴系統(tǒng)一些信息的:
1 /*下面是指定模塊版權(quán)、模塊作者、模塊簡要描述信息*/2 MODULE_LICENSE("GPL");3 MODULE_AUTHOR("jiayith"); 4 MODULE_DESCRIPTION("jiayith->A simple virtual char device."); 第3節(jié)、一些數(shù)據(jù)類型
首先先不看初始化及卸載函數(shù)都干了啥,先看看定義了什么數(shù)據(jù)類型。看:
1 /*下面是我的字符設(shè)備驅(qū)動程序的一些定義*/ 2 #ifndef MY_DATA_LEN 3 #define MY_DATA_LEN 8 //自定義設(shè)備描述符中數(shù)據(jù)的長度 4 #endif 5 6 /*自定義的設(shè)備描述結(jié)構(gòu)體*/ 7 struct MyCharDev 8 { 9 struct cdev myCDev; //struct cdev在<linux/cdev.h>中定義,描述一個字符設(shè)備10 char myData[MY_DATA_LEN]; //以后對設(shè)備的讀寫啊是對這塊內(nèi)存的操作,因為這是一個虛擬的設(shè)備11 };12 13 /*下面是幾個全局變量*/14 struct MyCharDev * pMyCharDev; //設(shè)備結(jié)構(gòu)體指針,因為是動態(tài)分配15 static dev_t myDev; //裝獲得的設(shè)備號,因為在多個函數(shù)里面都用到我定義了一個結(jié)構(gòu)體struct MyCharDev,第9行struct cdev結(jié)構(gòu)體是內(nèi)核描述字符設(shè)備的,因為內(nèi)核基本用c寫得嘛,沒有類,就用結(jié)構(gòu)體了。第10行我把它與一個字符數(shù)組放一起了,表示這個設(shè)備的存儲數(shù)據(jù)的空間,第0節(jié)不是說了嘛,這是個虛擬的設(shè)備,就用一塊內(nèi)存放設(shè)備的數(shù)據(jù)。這里有一個隱藏的問題的,struct cdev跟設(shè)備驅(qū)動程序關(guān)聯(lián),一個設(shè)備驅(qū)動程序只需要一個struct cdev就可以了,如果有多個設(shè)備怎么辦呢?多個設(shè)備讀寫的空間不是一樣嘛?先不管這個了,目前階段就當作只有一個設(shè)備。
dev_t,這個數(shù)據(jù)類型表示設(shè)備號的,是個typedef,typedef一個簡單類型。不是說有主次設(shè)備號嘛?怎么就一個變量就可以了?那是因為是按位操作的:32位機中是4個字節(jié),高12位表示主設(shè)備號,低20位表示次設(shè)備號。所以:
MAJOR(dev_t dev) 這個宏可以獲得主設(shè)備號;
MINOR(dev_t dev) 這個宏可以獲得次設(shè)備號;
MKDEV(int major,int minor) 又給定的參數(shù)獲得一個dev_t的類型的設(shè)備號。
第4節(jié)、模塊裝載初始化函數(shù)干了啥:

1 static int myCDevInit(void) //int my_init(void); 2 { 3 int res; //初始化函數(shù)的返回值 4 5 printk(KERN_EMERG "/n/n/nmyCDevInit() process.../n"); 6 7 /*動態(tài)分配設(shè)備號*/ 8 res=alloc_chrdev_region(&myDev,0,1,"MyCharDev"); 9 if(res<0) //表示分配設(shè)備號失敗10 {11 return res;12 }13 printk(KERN_EMERG "myCDevInit(): alloc_chrdev_region() success! major:%d,minor:%d/n", MAJOR(myDev), MINOR(myDev)); //打印獲得的主次設(shè)備號14 15 /*為設(shè)備描述結(jié)構(gòu)分配內(nèi)存*/16 pMyCharDev= kmalloc(sizeof(struct MyCharDev), GFP_KERNEL); 17 if(!pMyCharDev)18 {19 res=-ENOMEM; //系統(tǒng)定義的內(nèi)存不足20 goto failMalloc;21 } 22 memset(pMyCharDev,0,sizeof(struct MyCharDev));23 printk(KERN_EMERG "myCDevInit(): kmalloc() success!/n");24 25 /*下面初始化及注冊字符設(shè)備到系統(tǒng)中*/26 cdev_init(&(pMyCharDev->myCDev),&myCDevOps); //初始化struct cdev結(jié)構(gòu)27 cdev_add(&(pMyCharDev->myCDev),myDev,1); //注冊字符設(shè)備28 printk(KERN_EMERG "myCDevInit(): cdev_init() and cdev_add() success!/nmyCDevInit() process success!/n");29 30 return 0; //一切正常返回031 32 failMalloc: //內(nèi)存分配不足時跳到這里33 unregister_chrdev_region(myDev,1);34 return res;35 }模塊初始化函數(shù)a:分配設(shè)備號
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); /** * alloc_chrdev_region() - register a range of char device numbers * @dev: output parameter for first assigned number * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: the name of the associated device or driver * * Allocates a range of char device numbers. The major number will be * chosen dynamically, and returned (along with the first minor number) * in @dev. Returns zero or a negative error code. */alloc_chrdev_region
這個函數(shù)就是動態(tài)分配設(shè)備號的,設(shè)備號結(jié)果就在第一個參數(shù),其它參數(shù)含義見上。
b:為設(shè)備描述結(jié)構(gòu)分配內(nèi)存
我不是自己定義了struct MyCharDev嘛,其中的struct cdev要分配內(nèi)存存放,存儲區(qū)域也要分配內(nèi)存,kmallo()就是內(nèi)核態(tài)分配內(nèi)存的調(diào)用。
c:初始化及注冊字符設(shè)備到系統(tǒng)中
給struct MyCharDev分配內(nèi)存了,也就是也給系統(tǒng)描述字符設(shè)備的struct cdev分配內(nèi)存了,但是光分配不行啊,空當當?shù)臎]數(shù)據(jù)啊,所以要初始化struct cdev,cdev_init()就是干這個事情,初始化字符設(shè)備是也綁定了對這個設(shè)備進行讀寫操作時內(nèi)核要調(diào)用什么函數(shù),以后對這個設(shè)備的操作就直接調(diào)用這些函數(shù)就可以了,這個結(jié)構(gòu)體里面的賦值右側(cè)是自己寫的函數(shù)。這就是告訴系統(tǒng)一個綁定嘛。

1 /*自定義字符設(shè)備的操作函數(shù)的結(jié)構(gòu)體*/ 2 static const struct file_Operations myCDevOps = 3 { 4 .owner = THIS_MODULE, 5 .llseek = myCDevLlseek, 6 .read = myCDevRead, 7 .write = myCDevWrite, 8 .open = myCDevOpen, 9 .release = myCDevRelease10 };字符設(shè)備的操作函數(shù)然后把這個字符設(shè)備添加到系統(tǒng):cdev_add()。函數(shù)具體使用請搜搜。
第5節(jié)、模塊卸載函數(shù)干了啥:
1 /*退出函數(shù),當模塊從內(nèi)存卸載時被調(diào)用*/ 2 static void myCDevExit(void) //void my_exit(void); 3 { 4 printk(KERN_EMERG "myCDevExit() process.../n"); 5 6 cdev_del(&(pMyCharDev->myCDev)); //注銷設(shè)備 7 kfree(pMyCharDev); //釋放設(shè)備結(jié)構(gòu)體內(nèi)存 8 unregister_chrdev_region(myDev,1); //釋放設(shè)備號 9 10 printk(KERN_EMERG "myCDevExit() process success!/n"); 11 }其實就是與裝載時相反的事情啦,分配的內(nèi)存要回收吧?注冊了要注銷吧等。
第6節(jié)、其他的具體操作:
上面說了我們在內(nèi)存開辟了一個地方代表這是虛擬的字符設(shè)備的,對這個字符設(shè)備的讀寫啊什么的都是針對這個內(nèi)存空間的,我的就是一個字符數(shù)組啦,所以write()、read()什么的就好寫了嘛,直接裝到這個數(shù)組、從這個數(shù)組讀不久可以了嘛?具體看代碼的注釋吧,很詳細了,聰明的你一定會懂。
里面涉及到用戶態(tài)和內(nèi)核態(tài)的數(shù)據(jù)的交換,系統(tǒng)也給我們了,什么copy_from_user()之類的。
還有那些函數(shù)的參數(shù),這個系統(tǒng)規(guī)定了。
第7節(jié)、全部代碼:

1 /*包含我的電腦中已有的linux內(nèi)核源代碼,注意版本,我的路徑:/usr/src/linux-headers-3.13.0-30/include*/ 2 #include <linux/cdev.h> 3 #include <linux/module.h> 4 #include <linux/types.h> 5 #include <linux/init.h> 6 #include <linux/errno.h> 7 #include <linux/mm.h> 8 #include <linux/sched.h> 9 #include <linux/init.h> 10 #include <linux/fs.h> 11 #include <linux/kdev_t.h> 12 #include <linux/slab.h> 13 14 /*下面是我的字符設(shè)備驅(qū)動程序的一些定義*/ 15 #ifndef MY_DATA_LEN 16 #define MY_DATA_LEN 8 //自定義設(shè)備描述符中數(shù)據(jù)的長度 17 #endif 18 19 /*自定義的設(shè)備描述結(jié)構(gòu)體*/ 20 struct MyCharDev 21 { 22 struct cdev myCDev; //struct cdev在<linux/cdev.h>中定義,描述一個字符設(shè)備 23 char myData[MY_DATA_LEN]; //以后對設(shè)備的讀寫啊是對這塊內(nèi)存的操作,因為這是一個虛擬的設(shè)備 24 }; 25 26 /*下面是幾個全局變量*/ 27 struct MyCharDev * pMyCharDev; //設(shè)備結(jié)構(gòu)體指針,因為是動態(tài)分配 28 static dev_t myDev; //裝獲得的設(shè)備號,因為在多個函數(shù)里面都用到 29 30 31 /*文件打開函數(shù),無論一個進程何時試圖去打開這個設(shè)備都會調(diào)用這個函數(shù)*/ 32 int myCDevOpen(struct inode* inode, struct file* filp) 33 { 34 } 35 36 /*文件釋放函數(shù),當一個進程試圖關(guān)閉這個設(shè)備特殊文件的時候調(diào)用這個函數(shù)*/ 37 int myCDevRelease(struct inode* inode, struct file* filp) 38 { 39 } 40 41 42 /*讀函數(shù),當一個進程已經(jīng)打開次設(shè)備文件以后并且試圖去讀它的時候調(diào)用這個函數(shù)。從filp的ppos位置讀size個到用戶空間的buf中*/ 43 static ssize_t myCDevRead(struct file *filp, char __user *buf, size_t size, loff_t *ppos) 44 { 45 unsigned long pos = *ppos; /*記錄文件指針偏移位置*/ 46 unsigned int count = size; /*記錄需要讀取的字節(jié)數(shù)*/ 47 int ret = 0; /*返回值*/ 48 struct MyCharDev * pDev=filp->private_data; //獲得這個文件對應(yīng)的相當于私有的數(shù)據(jù) 49 50 /*判斷讀位置是否有效*/ 51 if(pos>=MY_DATA_LEN) //要讀取的偏移大于設(shè)備的內(nèi)存空間,也就是我自己定義的數(shù)組 52 { 53 return 0; 54 } 55 if(pos+count>MY_DATA_LEN) //無法滿足讀取這么多個字節(jié) 56 { 57 count=MY_DATA_LEN-pos; //盡量多地讀取 58 } 59 60 /*讀數(shù)據(jù)到用戶空間:內(nèi)核空間->用戶空間交換數(shù)據(jù)*/ 61 if(copy_to_user(buf,(void*)(pDev->myData+pos),count)) 62 { 63 ret= -EFAULT; 64 } 65 else 66 { 67 *ppos+=count; //把記錄文件讀取的位置移動到正確的位置,注意,這里可以看出*ppos范圍是[0,MY_DATA_LEN] 68 ret=count; //返回正確讀到的字符個數(shù) 69 70 printk(KERN_EMERG "myCDevRead():read %d byte(s) from %d position/n",count,pos); 71 } 72 73 return ret; 74 } 75 76 77 /*寫函數(shù),當試圖將數(shù)據(jù)寫入這個設(shè)備文件的時候,這個函數(shù)被調(diào)用。把用戶空間buf開始的size個寫入filp對應(yīng)的文件ppos位置*/ 78 static ssize_t myCDevWrite(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) 79 { 80 unsigned long pos = *ppos; 81 unsigned int count = size; 82 int ret = 0; 83 struct MyCharDev * pDev = filp->private_data; /*獲得設(shè)備結(jié)構(gòu)體指針*/ 84 85 /*分析和獲取有效的寫長度*/ 86 if(pos>=MY_DATA_LEN) 87 { 88 return 0; 89 } 90 if(pos+count>MY_DATA_LEN) 91 { 92 count=MY_DATA_LEN-pos; 93 } 94 95 /*從用戶空間寫入數(shù)據(jù)*/ 96 if(copy_from_user(pDev->myData+pos,buf,count)) 97 { 98 ret = -EFAULT; 99 }100 else101 {102 *ppos+=count;103 ret=count;104 105 printk(KERN_EMERG "myCDevWrite():write %d byte(s) from %d/n",count,pos); 106 }107 108 return ret;109 }110 111 /*seek文件定位函數(shù),為已經(jīng)打開的設(shè)備文件設(shè)置其偏移。把filp的偏移設(shè)置成從whence開始的加上offset*/112 static loff_t myCDevLlseek(struct file *filp, loff_t offset, int whence)113 {114 }115 116 117 /*自定義字符設(shè)備的操作函數(shù)的結(jié)構(gòu)體*/118 static const struct file_operations myCDevOps = 119 {120 .owner = THIS_MODULE, 121 .llseek = myCDevLlseek,122 .read = myCDevRead,123 .write = myCDevWrite,124 .open = myCDevOpen,125 .release = myCDevRelease126 };127 128 /*初始化函數(shù),當模塊裝載時被調(diào)用,如果裝載成功返回0,否則返回非0值*/129 static int myCDevInit(void) //int my_init(void);130 {131 int res; //初始化函數(shù)的返回值132 133 printk(KERN_EMERG "/n/n/nmyCDevInit() process.../n"); 134 135 /*動態(tài)分配設(shè)備號*/136 res=alloc_chrdev_region(&myDev,0,1,"MyCharDev");137 if(res<0) //表示分配設(shè)備號失敗138 {139 return res;140 }141 printk(KERN_EMERG "myCDevInit(): alloc_chrdev_region() success! major:%d,minor:%d/n", MAJOR(myDev), MINOR(myDev)); //打印獲得的主次設(shè)備號142 143 /*為設(shè)備描述結(jié)構(gòu)分配內(nèi)存*/144 pMyCharDev= kmalloc(sizeof(struct MyCharDev), GFP_KERNEL); 145 if(!pMyCharDev)146 {147 res=-ENOMEM; //系統(tǒng)定義的內(nèi)存不足148 goto failMalloc;149 } 150 memset(pMyCharDev,0,sizeof(struct MyCharDev));151 printk(KERN_EMERG "myCDevInit(): kmalloc() success!/n");152 153 /*下面初始化及注冊字符設(shè)備到系統(tǒng)中*/154 cdev_init(&(pMyCharDev->myCDev),&myCDevOps); //初始化struct cdev結(jié)構(gòu)155 cdev_add(&(pMyCharDev->myCDev),myDev,1); //注冊字符設(shè)備156 printk(KERN_EMERG "myCDevInit(): cdev_init() and cdev_add() success!/nmyCDevInit() process success!/n");157 158 return 0; //一切正常返回0159 160 failMalloc: //內(nèi)存分配不足時跳到這里161 unregister_chrdev_region(myDev,1);162 return res;163 }164 165 /*退出函數(shù),當模塊從內(nèi)存卸載時被調(diào)用*/166 static void myCDevExit(void) //void my_exit(void);167 {168 printk(KERN_EMERG "myCDevExit() process.../n"); 169 170 cdev_del(&(pMyCharDev->myCDev)); //注銷設(shè)備171 kfree(pMyCharDev); //釋放設(shè)備結(jié)構(gòu)體內(nèi)存172 unregister_chrdev_region(myDev,1); //釋放設(shè)備號173 174 printk(KERN_EMERG "myCDevExit() process success!/n"); 175 }176 177 178 module_init(myCDevInit); //通過module_init例程把模塊入口點myCDevInit注冊到系統(tǒng)中179 module_exit(myCDevExit); //由module_exit例程把模塊出口函數(shù)注冊到系統(tǒng)180 181 /*下面是指定模塊版權(quán)、模塊作者、模塊簡要描述信息*/182 MODULE_LICENSE("GPL");183 MODULE_AUTHOR("jiayith"); 184 MODULE_DESCRIPTION("jiayith->A simple virtual char device."); 源代碼
ifneq ($(KERNELRELEASE),)obj-m := myCDev.oelsePWD := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(PWD) modulesclean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*endifmakefile
第五章:啰嗦與參考資歷
看完了如果要寫的話,就倒著看這個博客。相信我解釋清楚了。
抄襲了:http://www.CUOXin.com/geneil/archive/2011/12/03/2272869.html
抄襲了:http://blog.chinaunix.net/uid-254237-id-2458604.html
抄襲了:http://blog.chinaunix.net/uid-11829250-id-337300.html
抄襲了一大堆,希望大家不要介意哈。
新聞熱點
疑難解答
圖片精選