一、 什麼是ioctl
ioctl是裝置驅動程式中對裝置的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對裝置的一些特性進行控制,例如序列槽的傳輸波特率、馬達的轉速等等。它的調用個數如下:
int ioctl(int fd, ind cmd, …);
其中fd是使用者程式打開裝置時使用open函數傳回的檔案标示符,cmd是使用者程式對裝置的控制指令,至于後面的省略号,那是一些補充參數,一般最多一個,這個參數的有無和cmd的意義相關。
ioctl函數是檔案結構中的一個屬性分量,就是說如果你的驅動程式提供了對ioctl的支援,使用者就可以在使用者程式中使用ioctl函數來控制裝置的I/O通道。
二、 ioctl的必要性
如果不用ioctl的話,也可以實作對裝置I/O通道的控制,但那是蠻擰了。例如,我們可以在驅動程式中實作write的時候檢查一下是否有特殊約定的資料流通過,如果有的話,那麼後面就跟着控制指令(一般在socket程式設計中常常這樣做)。但是如果這樣做的話,會導緻代碼分工不明,程式結構混亂,程式員自己也會頭昏眼花的。是以,我們就使用ioctl來實作控制的功能。要記住,使用者程式所作的隻是通過指令碼(cmd)告訴驅動程式它想做什麼,至于怎麼解釋這些指令和怎麼實作這些指令,這都是驅動程式要做的事情。
三、 ioctl如何實作
這是一個很麻煩的問題,我是能省則省。要說清楚它,沒有四五千字是不行的,是以我這裡是不可能把它說得非常清楚了,不過如果讀者對使用者程式是怎麼和驅動程式聯系起來感興趣的話,可以看我前一陣子寫的《write的奧秘》。讀者隻要把write換成ioctl,就知道使用者程式的ioctl是怎麼和驅動程式中的ioctl實作聯系在一起的了。我這裡說一個大概思路,因為我覺得《Linux裝置驅動程式》這本書已經說的非常清楚了,但是得花一些時間來看。
在驅動程式中實作的ioctl函數體内,實際上是有一個switch{case}結構,每一個case對應一個指令碼,做出一些相應的操作。怎麼實作這些操作,這是每一個程式員自己的事情。因為裝置都是特定的,這裡也沒法說。關鍵在于怎樣組織指令碼,因為在ioctl中指令碼是唯一聯系使用者程式指令和驅動程式支援的途徑。指令碼的組織是有一些講究的,因為我們一定要做到指令和裝置是一一對應的,這樣才不會将正确的指令發給錯誤的裝置,或者是把錯誤的指令發給正确的裝置,或者是把錯誤的指令發給錯誤的裝置。這些錯誤都會導緻不可預料的事情發生,而當程式員發現了這些奇怪的事情的時候,再來調試程式查找錯誤,那将是非常困難的事情。是以在Linux核心中是這樣定義一個指令碼的:
____________________________________
| 裝置類型(幻數) | 序列号 | 方向 |資料尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|
這樣一來,一個指令就變成了一個整數形式的指令碼;但是指令碼非常的不直覺,是以Linux Kernel中提供了一些宏。這些宏可根據便于了解的字元串生成指令碼,或者是從指令碼得到一些使用者可以了解的字元串以标明這個指令對應的裝置類型、裝置序列号、資料傳送方向和資料傳輸尺寸。
這些宏我就不在這裡解釋了,具體的形式請讀者察看Linux核心源代碼中的宏,檔案裡給這些宏做了完整的定義。這裡我隻多說一個地方,那就是"幻數"。"幻數"是一個字母,資料長度也是8,用一個特定的字母來标明裝置類型,這和用一個數字是一樣的,隻是更加利于記憶和了解。就是這樣,再沒有更複雜的了。 更多的說了也沒用,讀者還是看一看源代碼吧,推薦各位閱讀《Linux 裝置驅動程式》所帶源代碼中的short一例,因為它比較短小,功能比較簡單,可以看明白ioctl的功能和細節。
四、 cmd參數如何得出
這裡确實要說一說,cmd參數在使用者程式端由一些宏根據裝置類型、序列号、傳送方向、資料尺寸等生成,這個整數通過系統調用傳遞到核心中的驅動程式,再由驅動程式使用解碼宏從這個整數中得到裝置的類型、序列号、傳送方向、資料尺寸等資訊,然後通過switch{case}結構進行相應的操作。要透徹了解,隻能是通過閱讀源代碼,我這篇文章實際上隻是一個引子。cmd參數的組織還是比較複雜的,我認為要搞熟它還是得花不少時間的,但是這是值得的,因為驅動程式中最難的是對中斷的了解。
五、 小結
ioctl其實沒有什麼很難的東西需要了解,關鍵是了解cmd指令碼是怎麼在使用者程式裡生成并在驅動程式裡解析的,程式員最主要的工作量在switch{case}結構中,因為對裝置的I/O控制都是通過這一部分的代碼實作的。
***********************************************************************************************************************
一、ioctl的簡介:
雖然在檔案操作結構體"structfile_operations"中有很多對應的裝置操作函數,但是有些指令是實在找不到對應的操作函數。如CD-ROM的驅動,想要一個彈出光驅的操作,這種操作并不是所有的字元裝置都需要的,是以檔案操作結構體也不會有對應的函數操作。
出于這樣的原因,ioctl就有它的用處了————一些沒辦法歸類的函數就統一放在ioctl這個函數操作中,通過指定的指令來實作對應的操作。是以,ioctl函數裡面都實作了多個的對硬體的操作,通過應用層傳入的指令來調用相應的操作。
來個圖來說一下應用層與驅動函數的ioctl之間的聯系:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iMTN1MwUDNzQjMzkjMx8lN3gDNxATNy8CX1IzLcJTMwEDMy8CX05WZth2YhRHdh9CX0VmbugXauVXYulGaj5yZvxmYvw1LcpDc0RHaiojIsJye.png)
上面的圖可以看出,fd通過核心後找到對應的inode和file結構體指針并傳給驅動函數,而另外兩個參數卻沒有修改(類型改了沒什麼關系)。
簡單介紹一下函數:
int (*ioctl) (struct inode * node,struct file *filp, unsigned int cmd, unsigned long arg);
參數:
1)inode和file:ioctl的操作有可能是要修改檔案的屬性,或者通路硬體。要修改
檔案屬性的話,就要用到這兩個結構體了,是以這裡傳來了它們的指針。
2)cmd:指令,接下來要長篇大論地說。
3)arg:參數,接下來也要長篇大論。
傳回值:
1)如果傳入的非法指令,ioctl傳回錯誤号-EINVAL。
2)核心中的驅動函數傳回值都有一個預設的方法,隻要是正數,核心就會傻乎乎的認為這是正确的傳回,并把它傳給應用層,如果是負值,核心就會認為它是錯誤号了。
Ioctl裡面多個不同的指令,那就要看它函數的實作來決定傳回值了。打個比方,如果ioctl裡面有一個類似read的函數,那傳回值也就可以像read一樣傳回。
當然,不傳回也是可以的。
二、ioctl的cmd
說白了,cmd就是一個數,如果應用層傳來的數值在驅動中有對應的操作,這樣就就可以了。
來個最簡單的ioctl實作:3rd_char_4/1st
1)要先定義個指令,就用一個簡單的0,來個指令的頭檔案,驅動和應用函數都要包含這個頭檔案:
1 #ifndef _TEST_CMD_H
2 #define _TEST_CMD_H
3
4 #define TEST_CLEAR 0
5
6 #endif
2)驅動實作ioctl:
指令TEST_CLEAR的操作就是清空驅動中的kbuf。
122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)
123 {
124 int ret = 0;
125 struct _test_t *dev =filp->private_data;
126
127 switch(cmd){
128 case TEST_CLEAR:
129memset(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("errorcmd!\n");
136 ret = - EINVAL;
137 break;
138 }
139
140 return ret;
141 }
3)再來個應用程式:
1 #include
2 #include
3 #include
4 #include
5 #include
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再驗證
26 if(ret <0)
27 {
28 perror("read");
29 }
30
31 close(fd);
32 return 0;
33 }
注:這裡為了read傳回出錯,我修改了驅動的read、write函數的開始時的第一個
判斷,一看就知道了。
4)驗證一下:
[root: 1st]# insmodtest.ko
major[253] minor[0]
hello kernel
[root: 1st]# mknod /dev/test c 2530
[root: 1st]# ./app
[test_write]write 10 bytes,cur_size:[10]
[test_write]kbuf is [xiaobai]
read: No such device or address//哈哈!出錯了!因為沒資料讀取。
按照上面的方法來定義一個指令是完全可以的,但核心開發人員發現這樣有點不對勁。
如果有兩個不同的裝置,但它們的ioctl的cmd卻一樣的,哪天有誰不小心打開錯了,并且調用ioctl,這樣就完蛋了。因為這個檔案裡面同樣有cmd對應實作。
為了防止這樣的事情發生,核心對cmd又有了新的定義,規定了cmd都應該不一樣。
三、ioctl中的cmd
一個cmd被分為了4個段,每一段都有各自的意義,cmd的定義在。注:但實際上中隻是包含了,這說明了這是跟平台相關的,ARM的定義在,但這檔案也是包含别的檔案,千找萬找,終于找到了。
在中,cmd拆分如下:
解釋一下四部分,全部都在和ioctl-number.txt這兩個文檔有說明。
1)幻數:說得再好聽的名字也隻不過是個0~0xff的數,占8bit(_IOC_TYPEBITS)。這個數是用來區分不同的驅動的,像裝置号申請的時候一樣,核心有一個文檔給出一些推薦的或者已經被使用的幻數。
164 'w' all CERN SCIdriver
165 'y' 00-1F packet based userlevel communications
166
167 'z' 00-3F CAN buscard
168
169 'z' 40-7F CAN buscard
170
可以看到'x'是還沒有人用的,我就拿這個當幻數!
2)序數:用這個數來給自己的指令編号,占8bit(_IOC_NRBITS),我的程式從1開始排序。
3)資料傳輸方向:占2bit(_IOC_DIRBITS)。如果涉及到要傳參,核心要求描述一下傳輸的方向,傳輸的方向是以應用層的角度來描述的。
1)_IOC_NONE:值為0,無資料傳輸。
2)_IOC_READ:值為1,從裝置驅動讀取資料。
3)_IOC_WRITE:值為2,往裝置驅動寫入資料。
4)_IOC_READ|_IOC_WRITE:雙向資料傳輸。
4)資料大小:與體系結構相關,ARM下占14bit(_IOC_SIZEBITS),如果資料是int,核心給這個賦的值就是sizeof(int)。
強調一下,核心是要求按這樣的方法把cmd分類,當然你也可以不這樣幹,這隻是為了迎合核心的要求,讓自己的程式看上去很正宗。上面我的程式沒按要求照樣運作。
既然核心這樣定義cmd,就肯定有方法讓使用者友善定義:
_IO(type,nr) //沒有參數的指令
_IOR(type,nr,size)//該指令是從驅動讀取資料
_IOW(type,nr,size)//該指令是從驅動寫入資料
_IOWR(type,nr,size)//雙向資料傳輸
上面的指令已經定義了方向,我們要傳的是幻數(type)、序号(nr)和大小(size)。在這裡szie的參數隻需要填參數的類型,如int,上面的指令就會幫你檢測類型的正确然後指派sizeof(int)。
有生成cmd的指令就必有拆分cmd的指令:
_IOC_DIR(cmd)//從指令中提取方向
_IOC_TYPE(cmd)//從指令中提取幻數
_IOC_NR(cmd) //從指令中提取序數
_IOC_SIZE(cmd)//從指令中提取資料大小
越講就越複雜了,既然講到這,随便就講一下預定義指令。
預定義指令是由核心來識别并且實作相應的操作,換句話說,一旦你使用了這些指令,你壓根也不要指望你的驅動程式能夠收到,因為核心拿掉就把它處理掉了。
分為三類:
1)可用于任何檔案的指令
2)隻用于普通檔案的指令
3)特定檔案系統類型的指令
其實上面的我三類我也沒搞懂,反正我自己随便編了幾個數當指令都沒出錯,如果真的怕出錯,那就不要用别人已經使用的幻數就行了。
講了這麼多,終于要上程式了,修改一下上一個程式,讓它看起來比較有内涵。
/3rd_char/3rd_char_4/2nd
1)先改一下指令:
1 #ifndef _TEST_CMD_H
2 #define _TEST_CMD_H
3
4 #define TEST_MAGIC 'x'//定義幻數
5 #define TEST_MAX_NR 1//定義指令的最大序數,隻有一個指令當然是1
6
7 #define TEST_CLEAR_IO(TEST_MAGIC, 0)
8
9 #endif
2)既然這麼辛苦改了cmd,在驅動函數當然要做一些參數檢驗:
122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, unsigned longarg)
123 {
124 int ret = 0;
125 struct _test_t *dev =filp->private_data;
126
127
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:
133memset(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("errorcmd!\n");
140 ret = - EINVAL;
141 break;
142 }
143
144 return ret;
145 }
每個參數的傳入都會先檢驗一下幻數還有序數是否正确。
3)應用程式的驗證:
結果跟上一個完全一樣,因為指令的操作沒有修改
[root: 2nd]# insmodtest.ko
major[253] minor[0]
hello kernel
[root: 2nd]# mknod /dev/test c 2530
[root: 2nd]# ./app
[test_write]write 10 bytes,cur_size:[10]
[test_write]kbuf is [xiaobai]
read: No such device oraddress
五、ioctl中的arg之整數傳參。
上面講的例子都沒有使用ioctl的傳參。這裡先要說一下ioctl傳參的方式。
應用層的ioctl的第三個參數是"...",這個跟printf的"..."可不一樣,printf中是意味這你可以傳任意個數的參數,而ioctl最多也隻能傳一個,"..."的意思是讓核心不要檢查這個參數的類型。也就是說,從使用者層可以傳入任何參數,隻要你傳入的個數是1.
一般會有兩種的傳參方法:
1)整數,那可是省力又省心,直接使用就可以了。
2)指針,通過指針的就傳什麼類型都可以了,當然用起來就比較煩。
先說簡單的,使用整數作為參數:
例子,實作個指令,通過傳入參數更改偏移量,雖然llseek已經實作,這裡隻是想驗證一下正數傳參的方法。
1)先加個指令:
1 #ifndef _TEST_CMD_H
2 #define _TEST_CMD_H
3
4 #define TEST_MAGIC 'x'//定義幻數
5 #defineTEST_MAX_NR 2 //定義指令的最大序數
6
7 #define TEST_CLEAR_IO(TEST_MAGIC, 1)
8 #define TEST_OFFSET_IO(TEST_MAGIC, 2)
9
10 #endif
這裡有人會問了,明明你是要傳入參數,為什麼不用_IOW而用_IO定義指令呢?
原因有二:
1)因為定義資料的傳輸方向是為了好讓驅動的函數驗證資料的安全性,而一般指針才需要檢驗安全性,因為有人會惡意傳參(回想一下copy_to_user)。
2)個人喜好,友善我寫程式介紹另一種傳參方法,說白了指令也隻是一個數,隻要不要跟預定義指令沖突就可以了。
2)更新test_ioctl
122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)
123 {
124 int ret = 0;
125 struct _test_t *dev =filp->private_data;
126
127
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:
133memset(dev->kbuf, 0, DEV_SIZE);
134 dev->cur_size =0;
135 filp->f_pos =0;
136 ret = 0;
137 break;
138 case TEST_OFFSET://根據傳入的參數更改偏移量
139 filp->f_pos +=(int)arg;
140 P_DEBUG("changeoffset!\n");
141 ret = 0;
142 break;
143 default:
144 P_DEBUG("errorcmd!\n");
145 ret = - EINVAL;
146 break;
147 }
148
149 return ret;
150 }
TSET_OFFSET指令就是根據傳參更改偏移量,不過這裡要注意一個問題,那就是參數的類型,驅動函數必須要知道從應用傳來的參數是什麼類型,不然就沒法使用。在這個函數裡,從應用層傳來的參數是int,是以在驅動中也得用int。
3)再改一下應用程式:
1 #include
2 #include
3 #include
4 #include
5 #include
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);//再讀資料
27 printf(" buf is [%s]\n",buf);
28 if(ret <0)
29 {
30 perror("read");
31 }
32
33 close(fd);
34 return 0;
35 }
4)驗證一下
[root: 3rd]# insmodtest.ko
major[253] minor[0]
hello kernel
[root: 3rd]# mknod /dev/test c 2530
[root: 3rd]# ./app
[test_write]write 10 bytes,cur_size:[10]
[test_write]kbuf is [xiaobai]
[test_ioctl]change offset!//更改偏移量
[test_read]read 10 bytes,cur_size:[0] //沒錯誤,成功讀取!
buf is [xiao bai]
上面的傳參很簡單把,接下來說一下以指針傳參。
考慮到參數不可能永遠隻是一個正數這麼簡單,如果要傳多一點的東西,譬如是結構體,那就得用上指針了。
六、ioctl中的arg之指針傳參。
一講到從應用程式傳來的指針,就得想起我邪惡的傳入了非法指針的例子。是以,驅動程式中任何與應用層打交道的指針,都得先檢驗指針的安全性。
說到這檢驗又有兩種方法:
1)用的時候才檢驗。
2)一進來ioctl就檢驗。
先說用的時候檢驗,說白了就是用copy_xx_user系列函數,下面實作一下:
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'//定義幻數
12 #defineTEST_MAX_NR 3 //定義指令的最大序數
13
14 #define TEST_CLEAR_IO(TEST_MAGIC, 1)
15 #define TEST_OFFSET_IO(TEST_MAGIC, 2)
16 #defineTEST_KBUF _IO(TEST_MAGIC, 3)
17
18 #endif
這裡有定義多了一個函數,雖然這個指令是涉及到了指針的傳參,但我還是_IOW,還是那一句,現在還不需要用上。
該指令的操作是傳進一個結構體指針,驅動根據結構體的内容修改kbuf和cur_size和偏移量。
2)來個實作函數:
122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)
123 {
124 int ret = 0;
125 struct _test_t *dev =filp->private_data;
126 struct ioctl_dataval;
127
128
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:
134memset(dev->kbuf, 0, DEV_SIZE);
135 dev->cur_size =0;
136 filp->f_pos =0;
137 ret = 0;
138 break;
139 case TEST_OFFSET://根據傳入的參數更改偏移量
140 filp->f_pos +=(int)arg;
141 P_DEBUG("changeoffset!\n");
142 ret = 0;
143 break;
144 case TEST_KBUF://修改kbuf
145 if(copy_from_user(&val, (structioctl_data *)arg, sizeof(struct ioctl_data))){
146 ret = - EFAULT;
147 goto RET;
148 }
149memset(dev->kbuf, 0, DEV_SIZE);
150memcpy(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("errorcmd!\n");
157 ret = - EINVAL;
158 break;
159 }
160
161 RET:
162 return ret;
163 }
第145行,因為指針是從使用者程式傳來,是以必須檢查安全性。
3)來個應用程式
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(" buf is [%s]\n",buf);
33 if(ret <0)
34 {
35 perror("read");
36 }
37
38 close(fd);
39 return 0;
40 }
4)再來驗證一下:
[root: 4th]# ./app
[test_write]write 10 bytes,cur_size:[10]
[test_write]kbuf is [xiaobai]
[test_read]read 10 bytes,cur_size:[0]
buf is [123456789]//成功!
注:類似copy_xx_user的函數含有put_user、get_user等,我就不細說了。
下面說第二種方法:進入ioctl後使用access_ok檢測。
聲明一下:下面的驗證方法是不正确的。如果不想看下去的話,今天的内容已經講完了。
先說一下access_ok的使用
access_ok(type, addr,size)
使用:檢測位址的安全性
參數:
type:用于指定資料傳輸的方向,VERIFY_READ表示要讀取應用層資料,VERIFT_WRITE表示要往應用層寫如資料。注意:這裡和IORIOW的方向相反。如果既讀取又寫入,那就使用VERIFY_WRITE。
addr:使用者空間的位址
size:資料的大小
傳回值:
成功傳回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'//定義幻數
12 #defineTEST_MAX_NR 3 //定義指令的最大序數
13
14 #define TEST_CLEAR_IO(TEST_MAGIC, 1)
15 #define TEST_OFFSET_IO(TEST_MAGIC, 2)
16 #defineTEST_KBUF _IOW(TEST_MAGIC, 3, struct ioctl_data)
17
18 #endif
這裡終于要用_IOW了!
2)實作ioctl
122 int test_ioctl (struct inode*node, struct file *filp, unsigned int cmd, uns igned longarg)
123 {
124 int ret = 0;
125 struct _test_t *dev =filp->private_data;
126
127
128 if(_IOC_TYPE(cmd) !=TEST_MAGIC) return - EINVAL;
129 if(_IOC_NR(cmd)> TEST_MAX_NR) return - EINVAL;
130
131if(_IOC_DIR(cmd) & _IOC_READ)
132 ret =access_ok(VERIFY_WRITE, (void __user *)arg,_IOC_SIZE(cmd));
133 elseif(_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:
139memset(dev->kbuf, 0, DEV_SIZE);
140 dev->cur_size =0;
141 filp->f_pos =0;
142 ret = 0;
143 break;
144 case TEST_OFFSET://根據傳入的參數更改偏移量
145 filp->f_pos +=(int)arg;
146 P_DEBUG("changeoffset!\n");
147 ret = 0;
148 break;
149 case TEST_KBUF://修改kbuf
150memset(dev->kbuf, 0, DEV_SIZE);
151memcpy(dev->kbuf, ((struct ioctl_data*)arg)->buf,
152 ((structioctl_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("errorcmd!\n");
159 ret = - EINVAL;
160 break;
161 }
162
163 return ret;
164 }
上面并沒有用copy_to_user,而是通過access_ok來檢測。
3)再來個應用程式:
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(" buf is [%s]\n",buf);
37 if(ret <0)
38 {
39 perror("read");
40 }
41
42 close(fd);
43 return 0;
44 }
4)驗證一下:效果和上一個一樣
[root: 5th]# ./app
[test_write]write 10 bytes,cur_size:[10]
[test_write]kbuf is [xiaobai]
[test_read]read 10 bytes,cur_size:[0]
buf is [123456789]
下面就要如正題了,這個驅動是有問題的,那就是驗證安全性完全不起作用!當我傳入非法指針時,驅動同樣會輸出,不信可以自己傳個邪惡位址(void*)0進去試一下。
修改應用程式一樣代碼:
29 ret = ioctl(fd, TEST_KBUF,&my_data);
上面是我做的錯誤實作,我本來想驗證,隻要經過access_ok檢驗,資料就會安全,沒想到經過access_ok檢驗之後照樣會出錯。
但是,copy_to_user同樣是先調用access_ok再調用memcpy,它卻沒出錯。這個我事情我現在都沒搞明白,如果誰知道了麻煩指點一下。
我查了裝置驅動第三版,在144頁有這樣的說法:
1.access_ok并沒有做完的所有的記憶體檢查,
2.大多數的驅動代碼都不是用access_ok的,後面的記憶體管理會講述。
在這裡書本上有這樣的約定:(都是我自己的了解)
1.傳入指針需要檢查安全性。memcpy函數盡量不要在核心中使用。
2.copy_to_user,copy_from_user,get_user,put_user函數會再拷貝資料前檢測指針的安全性(檢查使用者空間的位址),不需要access_ok。copy_to_user,copy_from_user函數用于一塊資料,get_user,put_user函數用于傳遞單個資料(1、2、4及8個位元組)
3.如果在ioctl函數開頭使用了accsee_ok檢驗資料,接下來的代碼可以使用__put_user或__get_user這些不需要檢測的函數(書上有例子)
雖然還有寫東西還沒搞懂,但個人覺得,如果使用個access_ok要這麼麻煩的話,那我就不用好了,以後我就使用copy_xx_user函數,省力又省心。
七、總結:
這次講了ioctl的實作:
1)指令是怎麼定義。
2)參數怎麼傳遞。
=======================================================