linux設(shè)備驅(qū)動歸納總結(jié)(三):4.ioctl的實(shí)現(xiàn) 一、ioctl的簡介: 雖然在文件操作結(jié)構(gòu)體"struct file_operations"中有很多對應(yīng)的設(shè)備操作函數(shù),但是有些命令是實(shí)在找不到對應(yīng)的操作函數(shù)。如CD-ROM的驅(qū)動,想要一個彈出光驅(qū)的操作,這種操作并不是所有的字符設(shè)備都需要的,所以文件操作結(jié)構(gòu)體也不會有對應(yīng)的函數(shù)操作。 出于這樣的原因,ioctl就有它的用處了————一些沒辦法歸類的函數(shù)就統(tǒng)一放在ioctl這個函數(shù)操作中,通過指定的命令來實(shí)現(xiàn)對應(yīng)的操作。所以,ioctl函數(shù)里面都實(shí)現(xiàn)了多個的對硬件的操作,通過應(yīng)用層傳入的命令來調(diào)用相應(yīng)的操作。 來個圖來說一下應(yīng)用層與驅(qū)動函數(shù)的ioctl之間的聯(lián)系:
上面的圖可以看出,fd通過內(nèi)核后找到對應(yīng)的inode和file結(jié)構(gòu)體指針并傳給驅(qū)動函數(shù),而另外兩個參數(shù)卻沒有修改(類型改了沒什么關(guān)系)。 簡單介紹一下函數(shù): int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg); 參數(shù): 1)inode和file:ioctl的操作有可能是要修改文件的屬性,或者訪問硬件。要修改 文件屬性的話,就要用到這兩個結(jié)構(gòu)體了,所以這里傳來了它們的指針。 2)cmd:命令,接下來要長篇大論地說。 3)arg:參數(shù),接下來也要長篇大論。 返回值: 1)如果傳入的非法命令,ioctl返回錯誤號-EINVAL。 2)內(nèi)核中的驅(qū)動函數(shù)返回值都有一個默認(rèn)的方法,只要是正數(shù),內(nèi)核就會傻乎乎的認(rèn)為這是正確的返回,并把它傳給應(yīng)用層,如果是負(fù)值,內(nèi)核就會認(rèn)為它是錯誤號了。 Ioctl里面多個不同的命令,那就要看它函數(shù)的實(shí)現(xiàn)來決定返回值了。打個比方,如果ioctl里面有一個類似read的函數(shù),那返回值也就可以像read一樣返回。 當(dāng)然,不返回也是可以的。 二、ioctl的cmd 說白了,cmd就是一個數(shù),如果應(yīng)用層傳來的數(shù)值在驅(qū)動中有對應(yīng)的操作,這樣就就可以了。 來個最簡單的ioctl實(shí)現(xiàn):3rd_char_4/1st 1)要先定義個命令,就用一個簡單的0,來個命令的頭文件,驅(qū)動和應(yīng)用函數(shù)都要包含這個頭文件: /*test_cmd.h*/ 1 #ifndef _TEST_CMD_H 2 #define _TEST_CMD_H 3 4 #define TEST_CLEAR 0 5 6 #endif /*_TEST_CMD_H*/ 2)驅(qū)動實(shí)現(xiàn)ioctl: 命令TEST_CLEAR的操作就是清空驅(qū)動中的kbuf。 122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg) 123 { 124 int ret = 0; 125 struct _test_t *dev = filp->private_data; 126 127 switch(cmd){ 128 case TEST_CLEAR: 129 memset(dev->kbuf, 0, DEV_SIZE); 130 dev->cur_size = 0; 131 filp->f_pos = 0; 132 ret = 0; 133 break; 134 default: /*命令錯誤時的處理*/ 135 P_DEBUG("error cmd!\n"); 136 ret = - EINVAL; 137 break; 138 } 139 140 return ret; 141 } 3)再來個應(yīng)用程序: 1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <sys/ioctl.h> 6 #include "test_cmd.h" 7 8 int main(void) 9 { 10 char buf[20]; 11 int fd; 12 int ret; 13 14 fd = open("/dev/test", O_RDWR); 15 if(fd < 0) 16 { 17 perror("open"); 18 return -1; 19 } 20 21 write(fd, "xiao bai", 10); //1先寫入 22 23 ioctl(fd, TEST_CLEAR); //2再清空 24 25 ret = read(fd, buf, 10); //3再驗(yàn)證 26 if(ret < 0) 27 { 28 perror("read"); 29 } 30 31 close(fd); 32 return 0; 33 } 注:這里為了read返回出錯,我修改了驅(qū)動的read、write函數(shù)的開始時的第一個 判斷,一看就知道了。 4)驗(yàn)證一下: [root: 1st]# insmod test.ko major[253] minor[0] hello kernel [root: 1st]# mknod /dev/test c 253 0 [root: 1st]# ./app <kernel>[test_write]write 10 bytes, cur_size:[10] <kernel>[test_write]kbuf is [xiao bai] read: No such device or address //哈哈!出錯了!因?yàn)闆]數(shù)據(jù)讀取。 按照上面的方法來定義一個命令是完全可以的,但內(nèi)核開發(fā)人員發(fā)現(xiàn)這樣有點(diǎn)不對勁。 如果有兩個不同的設(shè)備,但它們的ioctl的cmd卻一樣的,哪天有誰不小心打開錯了,并且調(diào)用ioctl,這樣就完蛋了。因?yàn)檫@個文件里面同樣有cmd對應(yīng)實(shí)現(xiàn)。 為了防止這樣的事情發(fā)生,內(nèi)核對cmd又有了新的定義,規(guī)定了cmd都應(yīng)該不一樣。 三、ioctl中的cmd 一個cmd被分為了4個段,每一段都有各自的意義,cmd的定義在<linux/ioctl.h>。注:但實(shí)際上<linux/ioctl.h>中只是包含了<asm/ioctl.h>,這說明了這是跟平臺相關(guān)的,ARM的定義在<arch/arm/include/asm/ioctl.h>,但這文件也是包含別的文件<asm-generic/ioctl.h>,千找萬找,終于找到了。 在<asm-generic/ioctl.h>中,cmd拆分如下:
解釋一下四部分,全部都在<asm-generic/ioctl.h>和ioctl-number.txt這兩個文檔有說明。 1)幻數(shù):說得再好聽的名字也只不過是個0~0xff的數(shù),占8bit(_IOC_TYPEBITS)。這個數(shù)是用來區(qū)分不同的驅(qū)動的,像設(shè)備號申請的時候一樣,內(nèi)核有一個文檔給出一些推薦的或者已經(jīng)被使用的幻數(shù)。 /*Documentation/ioctl/ioctl-number.txt*/ 164 'w' all CERN SCI driver 165 'y' 00-1F packet based user level communications 166 <mailto:zapman@interlan.net> 167 'z' 00-3F CAN bus card 168 <mailto:hdstich@connectu.ulm.circular.de> 169 'z' 40-7F CAN bus card 170 <mailto:oe@port.de> 可以看到'x'是還沒有人用的,我就拿這個當(dāng)幻數(shù)! 2)序數(shù):用這個數(shù)來給自己的命令編號,占8bit(_IOC_NRBITS),我的程序從1開始排序。 3)數(shù)據(jù)傳輸方向:占2bit(_IOC_DIRBITS)。如果涉及到要傳參,內(nèi)核要求描述一下傳輸?shù)姆较?,傳輸?shù)姆较蚴且詰?yīng)用層的角度來描述的。 1)_IOC_NONE:值為0,無數(shù)據(jù)傳輸。 2)_IOC_READ:值為1,從設(shè)備驅(qū)動讀取數(shù)據(jù)。 3)_IOC_WRITE:值為2,往設(shè)備驅(qū)動寫入數(shù)據(jù)。 4)_IOC_READ|_IOC_WRITE:雙向數(shù)據(jù)傳輸。 4)數(shù)據(jù)大小:與體系結(jié)構(gòu)相關(guān),ARM下占14bit(_IOC_SIZEBITS),如果數(shù)據(jù)是int,內(nèi)核給這個賦的值就是sizeof(int)。 強(qiáng)調(diào)一下,內(nèi)核是要求按這樣的方法把cmd分類,當(dāng)然你也可以不這樣干,這只是為了迎合內(nèi)核的要求,讓自己的程序看上去很正宗。上面我的程序沒按要求照樣運(yùn)行。 既然內(nèi)核這樣定義cmd,就肯定有方法讓用戶方便定義: _IO(type,nr) //沒有參數(shù)的命令 _IOR(type,nr,size) //該命令是從驅(qū)動讀取數(shù)據(jù) _IOW(type,nr,size) //該命令是從驅(qū)動寫入數(shù)據(jù) _IOWR(type,nr,size) //雙向數(shù)據(jù)傳輸 上面的命令已經(jīng)定義了方向,我們要傳的是幻數(shù)(type)、序號(nr)和大小(size)。在這里szie的參數(shù)只需要填參數(shù)的類型,如int,上面的命令就會幫你檢測類型的正確然后賦值sizeof(int)。 有生成cmd的命令就必有拆分cmd的命令: _IOC_DIR(cmd) //從命令中提取方向 _IOC_TYPE(cmd) //從命令中提取幻數(shù) _IOC_NR(cmd) //從命令中提取序數(shù) _IOC_SIZE(cmd) //從命令中提取數(shù)據(jù)大小 越講就越復(fù)雜了,既然講到這,隨便就講一下預(yù)定義命令。 預(yù)定義命令是由內(nèi)核來識別并且實(shí)現(xiàn)相應(yīng)的操作,換句話說,一旦你使用了這些命令,你壓根也不要指望你的驅(qū)動程序能夠收到,因?yàn)閮?nèi)核拿掉就把它處理掉了。 分為三類: 1)可用于任何文件的命令 2)只用于普通文件的命令 3)特定文件系統(tǒng)類型的命令 其實(shí)上面的我三類我也沒搞懂,反正我自己隨便編了幾個數(shù)當(dāng)命令都沒出錯,如果真的怕出錯,那就不要用別人已經(jīng)使用的幻數(shù)就行了。 講了這么多,終于要上程序了,修改一下上一個程序,讓它看起來比較有內(nèi)涵。 /3rd_char/3rd_char_4/2nd 1)先改一下命令: /*test_cmd.h*/ 1 #ifndef _TEST_CMD_H 2 #define _TEST_CMD_H 3 4 #define TEST_MAGIC 'x' //定義幻數(shù) 5 #define TEST_MAX_NR 1 //定義命令的最大序數(shù),只有一個命令當(dāng)然是1 6 7 #define TEST_CLEAR _IO(TEST_MAGIC, 0) 8 9 #endif /*_TEST_CMD_H*/ 2)既然這么辛苦改了cmd,在驅(qū)動函數(shù)當(dāng)然要做一些參數(shù)檢驗(yàn): /*test.c*/ 122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, unsigned long arg) 123 { 124 int ret = 0; 125 struct _test_t *dev = filp->private_data; 126 127 /*既然這么費(fèi)勁定義了命令,當(dāng)然要檢驗(yàn)命令是否有效*/ 128 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL; 129 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL; 130 131 switch(cmd){ 132 case TEST_CLEAR: 133 memset(dev->kbuf, 0, DEV_SIZE); 134 dev->cur_size = 0; 135 filp->f_pos = 0; 136 ret = 0; 137 break; 138 default: /*命令錯誤時的處理*/ 139 P_DEBUG("error cmd!\n"); 140 ret = - EINVAL; 141 break; 142 } 143 144 return ret; 145 } 每個參數(shù)的傳入都會先檢驗(yàn)一下幻數(shù)還有序數(shù)是否正確。 3)應(yīng)用程序的驗(yàn)證: 結(jié)果跟上一個完全一樣,因?yàn)槊畹牟僮鳑]有修改 [root: 2nd]# insmod test.ko major[253] minor[0] hello kernel [root: 2nd]# mknod /dev/test c 253 0 [root: 2nd]# ./app <kernel>[test_write]write 10 bytes, cur_size:[10] <kernel>[test_write]kbuf is [xiao bai] read: No such device or address 五、ioctl中的arg之整數(shù)傳參。 上面講的例子都沒有使用ioctl的傳參。這里先要說一下ioctl傳參的方式。 應(yīng)用層的ioctl的第三個參數(shù)是"...",這個跟printf的"..."可不一樣,printf中是意味這你可以傳任意個數(shù)的參數(shù),而ioctl最多也只能傳一個,"..."的意思是讓內(nèi)核不要檢查這個參數(shù)的類型。也就是說,從用戶層可以傳入任何參數(shù),只要你傳入的個數(shù)是1. 一般會有兩種的傳參方法: 1)整數(shù),那可是省力又省心,直接使用就可以了。 2)指針,通過指針的就傳什么類型都可以了,當(dāng)然用起來就比較煩。 先說簡單的,使用整數(shù)作為參數(shù): 例子,實(shí)現(xiàn)個命令,通過傳入?yún)?shù)更改偏移量,雖然llseek已經(jīng)實(shí)現(xiàn),這里只是想驗(yàn)證一下正數(shù)傳參的方法。 1)先加個命令: 1 #ifndef _TEST_CMD_H 2 #define _TEST_CMD_H 3 4 #define TEST_MAGIC 'x' //定義幻數(shù) 5 #define TEST_MAX_NR 2 //定義命令的最大序數(shù) 6 7 #define TEST_CLEAR _IO(TEST_MAGIC, 1) 8 #define TEST_OFFSET _IO(TEST_MAGIC, 2) 9 10 #endif /*_TEST_CMD_H*/ 這里有人會問了,明明你是要傳入?yún)?shù),為什么不用_IOW而用_IO定義命令呢? 原因有二: 1)因?yàn)槎x數(shù)據(jù)的傳輸方向是為了好讓驅(qū)動的函數(shù)驗(yàn)證數(shù)據(jù)的安全性,而一般指針才需要檢驗(yàn)安全性,因?yàn)橛腥藭阂鈧鲄?FONT face="DejaVu Serif, serif">(回想一下copy_to_user)。 2)個人喜好,方便我寫程序介紹另一種傳參方法,說白了命令也只是一個數(shù),只要不要跟預(yù)定義命令沖突就可以了。 2)更新test_ioctl 122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg) 123 { 124 int ret = 0; 125 struct _test_t *dev = filp->private_data; 126 127 /*既然這么費(fèi)勁定義了命令,當(dāng)然要檢驗(yàn)命令是否有效*/ 128 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL; 129 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL; 130 131 switch(cmd){ 132 case TEST_CLEAR: 133 memset(dev->kbuf, 0, DEV_SIZE); 134 dev->cur_size = 0; 135 filp->f_pos = 0; 136 ret = 0; 137 break; 138 case TEST_OFFSET: //根據(jù)傳入的參數(shù)更改偏移量 139 filp->f_pos += (int)arg; 140 P_DEBUG("change offset!\n"); 141 ret = 0; 142 break; 143 default: /*命令錯誤時的處理*/ 144 P_DEBUG("error cmd!\n"); 145 ret = - EINVAL; 146 break; 147 } 148 149 return ret; 150 } TSET_OFFSET命令就是根據(jù)傳參更改偏移量,不過這里要注意一個問題,那就是參數(shù)的類型,驅(qū)動函數(shù)必須要知道從應(yīng)用傳來的參數(shù)是什么類型,不然就沒法使用。在這個函數(shù)里,從應(yīng)用層傳來的參數(shù)是int,因此在驅(qū)動中也得用int。 3)再改一下應(yīng)用程序: 1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <sys/ioctl.h> 6 7 #include "test_cmd.h" 8 9 int main(void) 10 { 11 char buf[20]; 12 int fd; 13 int ret; 14 15 fd = open("/dev/test", O_RDWR); 16 if(fd < 0) 17 { 18 perror("open"); 19 return -1; 20 } 21 22 write(fd, "xiao bai", 10); //先寫入 23 24 ioctl(fd, TEST_OFFSET, -10); //再改偏移量 25 26 ret = read(fd, buf, 10); //再讀數(shù)據(jù) 27 printf("<app> buf is [%s]\n", buf); 28 if(ret < 0) 29 { 30 perror("read"); 31 } 32 33 close(fd); 34 return 0; 35 } 4)驗(yàn)證一下 [root: 3rd]# insmod test.ko major[253] minor[0] hello kernel [root: 3rd]# mknod /dev/test c 253 0 [root: 3rd]# ./app <kernel>[test_write]write 10 bytes, cur_size:[10] <kernel>[test_write]kbuf is [xiao bai] <kernel>[test_ioctl]change offset! //更改偏移量 <kernel>[test_read]read 10 bytes, cur_size:[0] //沒錯誤,成功讀取! <app> buf is [xiao bai] 上面的傳參很簡單把,接下來說一下以指針傳參。 考慮到參數(shù)不可能永遠(yuǎn)只是一個正數(shù)這么簡單,如果要傳多一點(diǎn)的東西,譬如是結(jié)構(gòu)體,那就得用上指針了。 六、ioctl中的arg之指針傳參。 一講到從應(yīng)用程序傳來的指針,就得想起我邪惡的傳入了非法指針的例子。所以,驅(qū)動程序中任何與應(yīng)用層打交道的指針,都得先檢驗(yàn)指針的安全性。 說到這檢驗(yàn)又有兩種方法: 1)用的時候才檢驗(yàn)。 2)一進(jìn)來ioctl就檢驗(yàn)。 先說用的時候檢驗(yàn),說白了就是用copy_xx_user系列函數(shù),下面實(shí)現(xiàn)一下: 1)先定義個命令 1 #ifndef _TEST_CMD_H 2 #define _TEST_CMD_H 3 4 struct ioctl_data{ 5 unsigned int size; 6 char buf[100]; 7 }; 8 9 #define DEV_SIZE 100 10 11 #define TEST_MAGIC 'x' //定義幻數(shù) 12 #define TEST_MAX_NR 3 //定義命令的最大序數(shù) 13 14 #define TEST_CLEAR _IO(TEST_MAGIC, 1) 15 #define TEST_OFFSET _IO(TEST_MAGIC, 2) 16 #define TEST_KBUF _IO(TEST_MAGIC, 3) 17 18 #endif /*_TEST_CMD_H*/ 這里有定義多了一個函數(shù),雖然這個命令是涉及到了指針的傳參,但我還是_IOW,還是那一句,現(xiàn)在還不需要用上。 該命令的操作是傳進(jìn)一個結(jié)構(gòu)體指針,驅(qū)動根據(jù)結(jié)構(gòu)體的內(nèi)容修改kbuf和cur_size和偏移量。 2)來個實(shí)現(xiàn)函數(shù): 122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg) 123 { 124 int ret = 0; 125 struct _test_t *dev = filp->private_data; 126 struct ioctl_data val; 127 128 /*既然這么費(fèi)勁定義了命令,當(dāng)然要檢驗(yàn)命令是否有效*/ 129 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL; 130 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL; 131 132 switch(cmd){ 133 case TEST_CLEAR: 134 memset(dev->kbuf, 0, DEV_SIZE); 135 dev->cur_size = 0; 136 filp->f_pos = 0; 137 ret = 0; 138 break; 139 case TEST_OFFSET: //根據(jù)傳入的參數(shù)更改偏移量 140 filp->f_pos += (int)arg; 141 P_DEBUG("change offset!\n"); 142 ret = 0; 143 break; 144 case TEST_KBUF: //修改kbuf 145 if(copy_from_user(&val, (struct ioctl_data *)arg, sizeof(struct ioctl_data))){ 146 ret = - EFAULT; 147 goto RET; 148 } 149 memset(dev->kbuf, 0, DEV_SIZE); 150 memcpy(dev->kbuf, val.buf, val.size); 151 dev->cur_size = val.size; 152 filp->f_pos = 0; 153 ret = 0; 154 break; 155 default: /*命令錯誤時的處理*/ 156 P_DEBUG("error cmd!\n"); 157 ret = - EINVAL; 158 break; 159 } 160 161 RET: 162 return ret; 163 } 第145行,因?yàn)橹羔樖菑挠脩舫绦騻鱽?,所以必須檢查安全性。 3)來個應(yīng)用程序 9 int main(void) 10 { 11 char buf[20]; 12 int fd; 13 int ret; 14 15 struct ioctl_data my_data= { 16 .size = 10, 17 .buf = "123456789" 18 }; 19 20 fd = open("/dev/test", O_RDWR); 21 if(fd < 0) 22 { 23 perror("open"); 24 return -1; 25 } 26 27 write(fd, "xiao bai", 10); 28 29 ioctl(fd, TEST_KBUF, &my_data); 30 31 ret = read(fd, buf, 10); 32 printf("<app> buf is [%s]\n", buf); 33 if(ret < 0) 34 { 35 perror("read"); 36 } 37 38 close(fd); 39 return 0; 40 } 4)再來驗(yàn)證一下: [root: 4th]# ./app <kernel>[test_write]write 10 bytes, cur_size:[10] <kernel>[test_write]kbuf is [xiao bai] <kernel>[test_read]read 10 bytes, cur_size:[0] <app> buf is [123456789] //成功! 注:類似copy_xx_user的函數(shù)含有put_user、get_user等,我就不細(xì)說了。 下面說第二種方法:進(jìn)入ioctl后使用access_ok檢測。 聲明一下:下面的驗(yàn)證方法是不正確的。如果不想看下去的話,今天的內(nèi)容已經(jīng)講完了。 先說一下access_ok的使用 access_ok(type, addr, size) 使用:檢測地址的安全性 參數(shù): type:用于指定數(shù)據(jù)傳輸?shù)姆较颍?FONT face="DejaVu Serif, serif">VERIFY_READ表示要讀取應(yīng)用層數(shù)據(jù),VERIFT_WRITE表示要往應(yīng)用層寫如數(shù)據(jù)。注意:這里和IOR IOW的方向相反。如果既讀取又寫入,那就使用VERIFY_WRITE。 addr:用戶空間的地址 size:數(shù)據(jù)的大小 返回值: 成功返回1,失敗返回0。 既然知道怎么用,就直接來程序了: 1)定義命令 1 #ifndef _TEST_CMD_H 2 #define _TEST_CMD_H 3 4 struct ioctl_data{ 5 unsigned int size; 6 char buf[100]; 7 }; 8 9 #define DEV_SIZE 100 10 11 #define TEST_MAGIC 'x' //定義幻數(shù) 12 #define TEST_MAX_NR 3 //定義命令的最大序數(shù) 13 14 #define TEST_CLEAR _IO(TEST_MAGIC, 1) 15 #define TEST_OFFSET _IO(TEST_MAGIC, 2) 16 #define TEST_KBUF _IOW(TEST_MAGIC, 3, struct ioctl_data) 17 18 #endif /*_TEST_CMD_H*/ 這里終于要用_IOW了! 2)實(shí)現(xiàn)ioctl 122 int test_ioctl (struct inode *node, struct file *filp, unsigned int cmd, uns igned long arg) 123 { 124 int ret = 0; 125 struct _test_t *dev = filp->private_data; 126 127 /*既然這么費(fèi)勁定義了命令,當(dāng)然要檢驗(yàn)命令是否有效*/ 128 if(_IOC_TYPE(cmd) != TEST_MAGIC) return - EINVAL; 129 if(_IOC_NR(cmd) > TEST_MAX_NR) return - EINVAL; 130 /*根據(jù)提取命令指定的方向判斷指針的安全性*/ 131 if(_IOC_DIR(cmd) & _IOC_READ) 132 ret = access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); 133 else if(_IOC_DIR(cmd) & _IOC_WRITE) 134 ret = access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); 135 if(!ret) return - EFAULT; 136 137 switch(cmd){ 138 case TEST_CLEAR: 139 memset(dev->kbuf, 0, DEV_SIZE); 140 dev->cur_size = 0; 141 filp->f_pos = 0; 142 ret = 0; 143 break; 144 case TEST_OFFSET: //根據(jù)傳入的參數(shù)更改偏移量 145 filp->f_pos += (int)arg; 146 P_DEBUG("change offset!\n"); 147 ret = 0; 148 break; 149 case TEST_KBUF: //修改kbuf 150 memset(dev->kbuf, 0, DEV_SIZE); 151 memcpy(dev->kbuf, ((struct ioctl_data *)arg)->buf, 152 ((struct ioctl_data *)arg)->size); 153 dev->cur_size = ((struct ioctl_data *)arg)->size; 154 filp->f_pos = 0; 155 ret = 0; 156 break; 157 default: /*命令錯誤時的處理*/ 158 P_DEBUG("error cmd!\n"); 159 ret = - EINVAL; 160 break; 161 } 162 163 return ret; 164 } 上面并沒有用copy_to_user,而是通過access_ok來檢測。 3)再來個應(yīng)用程序: 9 int main(void) 10 { 11 char buf[20]; 12 int fd; 13 int ret; 14 15 struct ioctl_data my_data= { 16 .size = 10, 17 .buf = "123456789" 18 }; 19 20 fd = open("/dev/test", O_RDWR); 21 if(fd < 0) 22 { 23 perror("open"); 24 return -1; 25 } 26 27 write(fd, "xiao bai", 10); 28 29 ret = ioctl(fd, TEST_KBUF, &my_data); 30 if(ret < 0) 31 { 32 perror("ioctl"); 33 } 34 35 ret = read(fd, buf, 10); 36 printf("<app> buf is [%s]\n", buf); 37 if(ret < 0) 38 { 39 perror("read"); 40 } 41 42 close(fd); 43 return 0; 44 } 4)驗(yàn)證一下:效果和上一個一樣 [root: 5th]# ./app <kernel>[test_write]write 10 bytes, cur_size:[10] <kernel>[test_write]kbuf is [xiao bai] <kernel>[test_read]read 10 bytes, cur_size:[0] <app> buf is [123456789] 下面就要如正題了,這個驅(qū)動是有問題的,那就是驗(yàn)證安全性完全不起作用!當(dāng)我傳入非法指針時,驅(qū)動同樣會輸出,不信可以自己傳個邪惡地址(void *)0進(jìn)去試一下。 修改應(yīng)用程序一樣代碼: 29 ret = ioctl(fd, TEST_KBUF, &my_data); 上面是我做的錯誤實(shí)現(xiàn),我本來想驗(yàn)證,只要經(jīng)過access_ok檢驗(yàn),數(shù)據(jù)就會安全,沒想到經(jīng)過access_ok檢驗(yàn)之后照樣會出錯。 但是,copy_to_user同樣是先調(diào)用access_ok再調(diào)用memcpy,它卻沒出錯。這個我事情我現(xiàn)在都沒搞明白,如果誰知道了麻煩指點(diǎn)一下。 我查了設(shè)備驅(qū)動第三版,在144頁有這樣的說法: 1.access_ok并沒有做完的所有的內(nèi)存檢查, 2.大多數(shù)的驅(qū)動代碼都不是用access_ok的,后面的內(nèi)存管理會講述。 在這里書本上有這樣的約定:(都是我自己的理解) 1.傳入指針需要檢查安全性。memcpy函數(shù)盡量不要在內(nèi)核中使用。 2.copy_to_user.copy_from_user.get_user.put_user函數(shù)會再拷貝數(shù)據(jù)前檢測指針的安全性。不需要access_ok。 3.如果在ioctl函數(shù)開頭使用了accsee_ok檢驗(yàn)數(shù)據(jù),接下來的代碼可以使用__put_user或__get_user這些不需要檢測的函數(shù)(書上有例子) 雖然還有寫東西還沒搞懂,但個人覺得,如果使用個access_ok要這么麻煩的話,那我就不用好了,以后我就使用copy_xx_user函數(shù),省力又省心。 七、總結(jié): 這次講了ioctl的實(shí)現(xiàn): 1)命令是怎么定義。 2)參數(shù)怎么傳遞。 ======================================================= 源代碼: 3rd_char_4.rar |
|