linux 设备驱动总结,linux设备驱动归纳总结(三):3面向对象思想和lseek
linux設(shè)備驅(qū)動(dòng)歸納總結(jié)(三):3.設(shè)備驅(qū)動(dòng)面向?qū)ο笏枷牒蚻seek的實(shí)現(xiàn)
一、結(jié)構(gòu)體struct
file和struct inode
在之前寫的函數(shù),全部是定義了一些零散的全局變量。有沒有辦法整合成到一個(gè)結(jié)構(gòu)體當(dāng)中?這樣的話,看起來和用起來都比較方便。接下來就要說這方面的問題。
不過先要介紹一下除了fops以外的兩個(gè)比較重要的結(jié)構(gòu)體:
1)struct
file
在內(nèi)核中,file結(jié)構(gòu)體是用來維護(hù)打開的文件的。每打開一次文件,內(nèi)核空間里就
會(huì)多增加一個(gè)file來維護(hù),當(dāng)文件關(guān)閉是釋放。
所以,在內(nèi)核中可以存在同一個(gè)文件的多個(gè)file,因?yàn)樵撐募粦?yīng)用程序打開被打
開。
在struct
file中有幾個(gè)重要的成員:
1)loff_tf_pos;
這是用來記錄文件的偏移量。在應(yīng)用程序中,打開文件時(shí)偏移量為0,每次的讀寫操作都會(huì)使偏移量增加。
從這個(gè)原因可以看出為什么每打開一次文件就新建一個(gè)file結(jié)構(gòu)體了。不然的話,每個(gè)打開文件的讀寫操作都修改同一個(gè)偏移量,那讀寫豈不是亂套了嗎?
2)void*private_data;
這是空類型的指針可以用于存放任何數(shù)據(jù),我會(huì)用這個(gè)指針來存放待會(huì)要定義的結(jié)構(gòu)體指針。
回想一下,文件操作結(jié)構(gòu)體fops中所有的函數(shù)成員里面都有一個(gè)參數(shù)是file結(jié)構(gòu)體,所以每個(gè)函數(shù)都可以在file->private_data中拿到我自己定義的結(jié)構(gòu)體了。
3)struct file_operations *fops;
打開文件后,內(nèi)核會(huì)把fops存放在這里,以后的操作就在這里在這里找函數(shù)了。
2)struct
inode
這個(gè)結(jié)構(gòu)體是用來保存一個(gè)文件的基本信息的結(jié)構(gòu)體,即使打開多個(gè)相同的文件,也只會(huì)有一個(gè)對(duì)應(yīng)的inode。
它也有兩個(gè)常用的成員:
1)dev_t i_rdev;
這里存放著這個(gè)文件的設(shè)備號(hào)。
2)struct cdev *i_cdev;
這個(gè)結(jié)構(gòu)體很熟悉吧,這就是注冊(cè)設(shè)備時(shí)用的cdev就存在這。這個(gè)結(jié)構(gòu)體的用處現(xiàn)在我還不好說,待會(huì)看程序就知道了。
二、面向?qū)ο蟮乃枷?/p>
接下來就封裝一下之前程序的數(shù)據(jù)類型吧:
18 struct _test_t{
19 char
kbuf[DEV_SIZE];//這里存放數(shù)據(jù)
20 unsigned int
major;//這里存放主設(shè)備號(hào)
21 unsigned int
minor;//這里存放次設(shè)備號(hào)
22 unsigned int
cur_size;//這里存放當(dāng)前的kbuf的大小
23 dev_t devno;//這里存放設(shè)備號(hào)
24 struct cdev
test_cdev;//這里存放cdev結(jié)構(gòu)體
25 };
定義了這樣的一個(gè)結(jié)構(gòu)體后,在操作函數(shù)中怎么拿到這個(gè)結(jié)構(gòu)體的指針呢?
先來個(gè)函數(shù):
#define container_of(ptr, type,
member) ({\
const typeof( ((type *)0)->member
) *__mptr = (ptr);\
(type *)( (char *)__mptr -
offsetof(type,member) );})
使用:
已知一個(gè)結(jié)構(gòu)體里面一個(gè)成員的指針ptr,同時(shí),這個(gè)成員也是另外一個(gè)結(jié)構(gòu)體類型中的一個(gè)成員,這個(gè)結(jié)構(gòu)體的類型是type,而這個(gè)成員以member這個(gè)名字命名。就可以通過這個(gè)函數(shù)找到指向類型是type的結(jié)構(gòu)體的指針。
返回值:
返回值就是指向type結(jié)構(gòu)體類型的數(shù)據(jù)的指針。
如:現(xiàn)在定義這樣的兩個(gè)結(jié)構(gòu)體:
struct A {
int *xiaobai_a;
};
struct B {
int xiaobai_b;
};
struct A a;
在遙遠(yuǎn)的另一處有這樣的定義:struct
B b;
并且,a.xiaobai_a = &b.xiaobai_b;
這樣,在不知道b只知道a的情況下也可以找到b的位置:
struct B *bb =
container_of(a.xiaobai_a, struct B, xiaobai_b);
估計(jì)被上面的解釋說暈了吧。我還是舉個(gè)例比較方便:
雖然一個(gè)函數(shù)不值得說這么久,但是我覺得這種思想很不錯(cuò),內(nèi)核中很多時(shí)候都用到這個(gè)函數(shù),如在內(nèi)核鏈表中。
來個(gè)邪惡的例子名字——老板與小秘:
老板他請(qǐng)了個(gè)年輕的小秘,他就跟客戶說:“我電話號(hào)碼經(jīng)常換,你記著我小秘的電話,想找我嘛,找我小秘就可以了!”
于是,客戶想找老板了,就打通小秘的電話,說:“我知道你是秘書小紅,我想找你老板小黑,麻煩給他的電話號(hào)碼我。”
這樣,客戶就拿到了老板最新的電話號(hào)碼了。
想象老板和客戶是個(gè)結(jié)構(gòu)體,秘書和他的電話號(hào)碼是個(gè)各自成員,電話號(hào)碼想象成指針:
老板的電話 =
container_of(秘書的電話, 老板,小秘)
說了半天還沒進(jìn)入正題,這個(gè)函數(shù)用在哪里呢?誰當(dāng)小秘呢?
就是那個(gè)說了半天都不知道能做什么還經(jīng)常出現(xiàn)的struct
cdev!
而我把cdev添加到了我自己建的結(jié)構(gòu)體struct
_test_t中,所喲struct
_test_t就是老板!
而struct
inode就是客戶了,因?yàn)樗某蓡T里面有小秘的電話號(hào)碼:struct
cdev *i_cdev;
所以,如果想得到_test_t,只要調(diào)用這個(gè)函數(shù)就行了。
下面看一下改良后的open函數(shù)
27 int test_open(struct inode *node,
struct file *filp)
28 {
29 struct _test_t *dev;
30 dev =
container_of(node->i_cdev, struct _test_t, test_cdev);
31 filp->private_data
= dev;
32 return 0;
33 }
上面還有一句,將獲得的結(jié)構(gòu)體指針存放到filp的private_data中。
這是因?yàn)?#xff0c;struct
file_operations中的每個(gè)函數(shù)的第一個(gè)參數(shù)就是struct
file,只要有file,每個(gè)函數(shù)都可以從private_data中得到數(shù)據(jù)了。相反,struct
inode這個(gè)參數(shù)并不是file_operations中所有的函數(shù)都有。
下面貼上部分代碼:1st/test.c
18 struct _test_t{
19 char kbuf[DEV_SIZE];
20 unsigned int major;
21 unsigned int minor;
22 unsigned int cur_size;
23 dev_t devno;
24 struct cdev test_cdev;
25 };
26
27 int test_open(struct inode *node,
struct file *filp)
28
{/*open操作需要給把拿到的結(jié)構(gòu)體指針賦值給private_data*/
29 struct _test_t *dev;
30 dev =
container_of(node->i_cdev, struct _test_t, test_cdev);
31 filp->private_data = dev;
32 return 0;
33 }
34
35 int test_close(struct inode
*node, struct file *filp)
36 {
37 return 0;
38 }
39
40 ssize_t test_read(struct file
*filp, char __user *buf, size_t count, loff_t *offset)
41 {
42 int ret;
43 struct _test_t *dev =
filp->private_data;
44
45 if(!dev->cur_size){
46 return 0;
47 }
48
49 if (copy_to_user(buf,
dev->kbuf, count)){
50 ret = - EFAULT;
51
}else{/*read函數(shù)成功讀取后要修改cur_size*/
52 ret = count;
53 dev->cur_size -=
count;
54 }
55 P_DEBUG("cur_size:[%d]\n",
dev->cur_size);
56
57 return ret;
58 }
59
60 ssize_t test_write(struct file
*filp, const char __user *buf, size_t count, loff_t *offset)
61 {
62 int ret;
63 struct _test_t *dev =
filp->private_data;
64
65 if(copy_from_user(dev->kbuf,
buf, count)){
66 ret = - EFAULT;
67
}else{/*write函數(shù)成功寫入后也要修改cur_size*/
68 ret = count;
69 dev->cur_size +=
count;
70 P_DEBUG("kbuf is
[%s]\n", dev->kbuf);
71
P_DEBUG("cur_size:[%d]\n", dev->cur_size);
72 }
73
74 return ret;
//返回實(shí)際寫入的字節(jié)數(shù)或錯(cuò)誤號(hào)
75 }
上面的程序其實(shí)就多了比上一個(gè)程序多了三步:
1)封裝了一個(gè)結(jié)構(gòu)體。
2)open函數(shù)要獲得結(jié)構(gòu)體并存放到private_data中。
3)read和write函數(shù)成功后要更新cur_size這個(gè)值。
這樣,一個(gè)像樣點(diǎn)的程序出來了,寫個(gè)應(yīng)用程序驗(yàn)證一下:
1 #include
2 #include
3 #include
4 #include
5
6 int main(void)
7 {
8 char buf[20];
9 int fd;
10 fd = open("/dev/test",
O_RDWR);
11 if(fd < 0)
12 {
13 perror("open");
14 return -1;
15 }
16
17 read(fd, buf, 10);
18 printf("buf
is [%s]\n", buf);
19
20 write(fd, "xiao bai",
10);
21
22 read(fd, buf, 10);
23 printf("buf
is [%s]\n", buf);
24
25 close(fd);
26 return 0;
27 }
運(yùn)行一下:
[root: 1st]# insmod test.ko
major[253] minor[0]
hello kernel
[root: 1st]# mknod /dev/test c 253 0
[root: 1st]# ./app
buf is
[]//第一次讀取時(shí)cur_size==0,沒數(shù)據(jù)就會(huì)返回
[test_write]kbuf is
[xiao bai]//成功寫入
[test_write]cur_size:[10]//更新cur_size
[test_read]cur_size:[0]//read讀取成功,跟新cur_size
buf is [xiao
bai]//應(yīng)用程序返回讀到的內(nèi)容
[root: 1st]#
三、read、write的改進(jìn)
上面的函數(shù)還是不完善的,想象一下,平時(shí)的read、write函數(shù)會(huì)增加偏移量,但上面的函數(shù)是不會(huì)的。這是因?yàn)檫€有一個(gè)參數(shù)我沒用上,就是"loff_t
offset"。
"loff_t
offset"這個(gè)參數(shù)是內(nèi)核在調(diào)用函數(shù)時(shí),從"struct
file"的成員"f_ops"拿到指針并當(dāng)作參數(shù)傳入。這樣的做法讓用戶不用再從"struct
file"提取成員,直接拿參數(shù)用就行了!
通過這個(gè)參數(shù),我們就可以改進(jìn)并且實(shí)現(xiàn)三個(gè)函數(shù):
1test_read:當(dāng)應(yīng)用程序調(diào)用read時(shí)內(nèi)核會(huì)調(diào)用test_read。讀取數(shù)據(jù)的同時(shí),偏移量會(huì)增加。
2test_write:當(dāng)應(yīng)用程序調(diào)用write時(shí)內(nèi)核會(huì)調(diào)用test_write。寫入數(shù)據(jù)的同時(shí),偏移量也會(huì)增加。
3test_llseek:這是跟應(yīng)用程序的lseek對(duì)應(yīng)的,用來修改偏移量的位置。
有了上面的三個(gè)函數(shù)的功能,這樣才算是個(gè)像樣的函數(shù)!
先改進(jìn)一下read、write函數(shù)
40 ssize_t test_read(struct file
*filp, char __user *buf, size_t count, loff_t *offset)
41 {
42 int ret;
43 struct _test_t *dev =
filp->private_data;
44
45 if(*offset >=
DEV_SIZE){//如果偏移量已經(jīng)超過了數(shù)組的容量
46 return count ? - ENXIO :
0; //count為0則返回0,表示讀取0個(gè)數(shù)據(jù)成功
47 }
//count不為0則分會(huì)錯(cuò)誤號(hào),地址越界
48 if(*offset + count >
DEV_SIZE){ //如果讀取字節(jié)數(shù)超過了最大偏移量
49 count = DEV_SIZE -
*offset; //則減少讀取字節(jié)數(shù)。
50 }
51 /*copy_to_user的參數(shù)也要改一下*/
52 if (copy_to_user(buf,
dev->kbuf + *offset, count)){
53 ret = - EFAULT;
54 }else{
55 ret = count;
56 dev->cur_size -=
count; //讀取后數(shù)組的字節(jié)數(shù)減少
57 *offset
+= count; //偏移量增加
58 P_DEBUG("read %d
bytes, cur_size:[%d]\n", count,dev->cur_size);
59 }
60
61 return ret;
//返回實(shí)際寫入的字節(jié)數(shù)或錯(cuò)誤號(hào)
62 }
63
64 ssize_t test_write(struct file
*filp, const char __user *buf, size_t count, loff_t *offset)
65 {
66 int ret;
67 struct _test_t *dev =
filp->private_data;
68 /*copy_from_user的參數(shù)也要改一下*/
69 if(*offset >=
DEV_SIZE){//如果偏移量已經(jīng)超過了數(shù)組的容量
70 return count ? - ENXIO :
0; //count為0則返回0,表示讀取0個(gè)數(shù)據(jù)成功
71 }
//count不為0則分會(huì)錯(cuò)誤號(hào),地址越界
72 if(*offset + count >
DEV_SIZE){ //如果讀取字節(jié)數(shù)超過了最大偏移量
73 count = DEV_SIZE -
*offset; //則減少讀取字節(jié)數(shù)。
74 }
75
76 if(copy_from_user(dev->kbuf,
buf, count)){
77 ret = - EFAULT;
78 }else{
79 ret = count;
80 dev->cur_size +=
count; //寫入后數(shù)組的字節(jié)數(shù)增加
81 *offset
+= count; //偏移量增加
82 P_DEBUG("write %d
bytes, cur_size:[%d]\n", count, dev->cur_size);
83 P_DEBUG("kbuf is
[%s]\n", dev->kbuf);
84 }
85
86 return ret;
//返回實(shí)際寫入的字節(jié)數(shù)或錯(cuò)誤號(hào)
87 }
話說得好,越是需要檢測出錯(cuò),代碼就會(huì)幾何級(jí)增加,如果不想看這么多代碼,把這兩個(gè)函數(shù)前面的兩個(gè)if(45-50、69-74)都刪掉!反正寫應(yīng)用程序的時(shí)候小心翼翼一點(diǎn)就好了。這個(gè)程序只是為了驗(yàn)證"offset"的作用。
再來個(gè)小心翼翼的應(yīng)用程序:
1 #include
2 #include
3 #include
4 #include
5
6 int main(void)
7 {
8 char buf[20];
9 int fd;
10 fd = open("/dev/test",
O_RDWR);
11 if(fd < 0)
12 {
13 perror("open");
14 return -1;
15 }
16
17 write(fd, "xiao bai",
10);
18
19 read(fd, buf, 10);
20 printf("buf
is [%s]\n", buf);
21
22 close(fd);
23 return 0;
24 }
驗(yàn)證一下:
[root: 2nd]# insmod test.ko
major[253] minor[0]
hello kernel
[root: 2nd]# mknod /dev/test c 253 0
[root: 2nd]# ./app
[test_write]write 10
bytes, cur_size:[10]//寫入
[test_write]kbuf is
[xiao bai]
[test_read]read 10
bytes, cur_size:[0]//但讀不出,因?yàn)槠屏吭黾?/p>
buf is []
上面的read函數(shù)根本讀不出數(shù)據(jù),這是因?yàn)槠屏吭黾恿恕_@個(gè)時(shí)候需要一個(gè)函數(shù)來把偏移量移到開頭,lseek函數(shù)就用上場了。下面就講一下。
四、lseek函數(shù)的實(shí)現(xiàn)
應(yīng)用層的函數(shù)lseek函數(shù)對(duì)應(yīng)驅(qū)動(dòng)的函數(shù)是llseek(為什么多了一個(gè)l我也想不懂)。
內(nèi)核驅(qū)動(dòng):loff_t (*llseek)
(struct file * filp, loff_t offset, int whence);
對(duì)應(yīng)應(yīng)用層:off_t lseek(int
fd, off_t offset, int whence);
使用:
一看參數(shù)就知道,這兩個(gè)函數(shù)的第二和第三個(gè)參數(shù)就是對(duì)應(yīng)的,當(dāng)應(yīng)用層調(diào)用函數(shù)時(shí),對(duì)應(yīng)的參數(shù)就會(huì)讓內(nèi)核傳給驅(qū)動(dòng)的函數(shù)llseek。
參數(shù):
offset:一看這個(gè)參數(shù)不是指針,就知道和read、write的參數(shù)不一樣。這是應(yīng)用層傳來的參數(shù),并不是"struct
file"的偏移量"f_ops"。
whence:這個(gè)也跟應(yīng)用層的參數(shù)一樣,指定從哪個(gè)位置開始偏移。
從開頭位置:#define
SEEK_SET0
從當(dāng)前位置:#define
SEEK_CUR1
從文件末端:#define
SEEK_END2
返回值:成功返回當(dāng)前的更新的偏移量,失敗返回錯(cuò)誤號(hào),而應(yīng)用層會(huì)返回-1。
下面來個(gè)程序:/3rd_char/3rd_char_3/3rd/test.c
/*test_llseek*/
89 loff_t test_llseek (struct file
*filp, loff_t offset, int whence)
90 {
91 loff_t new_pos;
//新偏移量
92 loff_t old_pos = filp->f_pos;
//舊偏移量
93
94 switch(whence){
95 case SEEK_SET:
96 new_pos = offset;
97 break;
98 case SEEK_CUR:
99 new_pos = old_pos +
offset;
100 break;
101 case SEEK_END:
102 new_pos = DEV_SIZE +
offset;
103 break;
104 default:
105 P_DEBUG("unknow
whence\n");
106 return - EINVAL;
107 }
108
109 if(new_pos < 0 || new_pos
> DEV_SIZE){ //如果偏移量越界,返回錯(cuò)誤號(hào)
110 P_DEBUG("f_pos
failed\n");
111 return - EINVAL;
112 }
113
114 filp->f_pos = new_pos;
115 return
new_pos;//正確返回新的偏移量
116 }
再來個(gè)應(yīng)用程序:/3rd_char/3rd_char_3/3rd/app.c
1 #include
2 #include
3 #include
4 #include
5
6 int main(void)
7 {
8 char buf[20];
9 int fd;
10 int ret;
11
12 fd = open("/dev/test",
O_RDWR);
13 if(fd < 0)
14 {
15 perror("open");
16 return -1;
17 }
18
19 write(fd, "xiao bai",
10);
20 /*讓偏移量移至開頭,這樣才能讀取數(shù)據(jù)*/
21 ret = lseek(fd, 0, SEEK_SET);
22
23 read(fd, buf, 10);
24 printf("buf
is [%s]\n", buf);
25
26 close(fd);
27 return 0;
28 }
驗(yàn)證一下:
[root: 2nd]# ./app
[test_write]write 10
bytes, cur_size:[10]
[test_write]kbuf is
[xiao bai]
[test_read]read 10
bytes, cur_size:[0]//讀到數(shù)據(jù)了!
buf is [xiao
bai]//讀到數(shù)據(jù)了!
五、總結(jié)
拉風(fēng)的時(shí)序圖我就不畫了。
上面講的東西不多:
1)container_of的使用
2)怎么使用偏移量"filp->f_ops"。
3)llseek的編寫。
=========================================================
總結(jié)
以上是生活随笔為你收集整理的linux 设备驱动总结,linux设备驱动归纳总结(三):3面向对象思想和lseek的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win8计算机管理员权限删除文件,win
- 下一篇: linux 2.6.35 内核配置 us