Linux內核驅動學習(三)字符型設備驅動之初體驗
linux字符型設備驅動之初體驗
文章目錄
Linux字符型設備驅動之初體驗
前言
框架
字符型設備
程序實現
cdev
kobj
owner
file_operations
dev_t
設備注冊過程
申請設備號
注冊設備
register_device
如何構建
模塊編譯
內核編譯
Makefile
Kconfig
總結
參考
前言
驅動總共分為字符型設備驅動,塊設備驅動,網絡設備驅動。對于字符型設備驅動的資料,網上比較多,《Linux Kernel Driver》這本書可以了解一下,對于學習Linux驅動有很大的幫助,當然還有很多優秀的書籍,暫不一一列舉,本文簡單總結了在學習字符型設備驅動的過程中遇到的問題,以及對該類驅動的理解。
框架
字符型設備
什么是字符型設備?字符型以字符(Byte/Char)為單位進行數據傳輸的設備,如鍵盤,串口等等設備,所以Linux環境編程中文件I/O進行操作的系統接口如open,read,write,close等等,在字符型設備驅動中同樣需要支持這些接口。這里會用到file_operations結構體,在后面會講到。
程序實現
下面是一個簡單字符型設備驅動程序,可以在系統注冊一個字符型設備驅動,目前未實現open,read,write,close等接口。
#include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
cdev
在linux/cdev.h中可以閱讀相關字符型設備驅動的信息,其中包括cdev結構體可以做一下分析,先定位到源碼做一下分析
#ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其中包括結構體cdev和cdev的一系列函數接口cdev_init,cdev_alloc,cdev_put,cdev_add,cdev_del,cd_forget。
kobject是所有設備驅動模型的基類,而cdev可以理解為是它的派生類,這里使用了面向對象的思想,通過訪問cdev中的kobj成員,就能使用kobject中所有功能。關于kobject的詳細內容可以參考內核文檔Documentation/kobject.txt。
首先明確一點的是owner是struct module的指針變量,owner=THIS_MODULE;,這里將指針指向當前的模塊,關于THIS_MODULE以及struct module的知識可以參考這篇博客。
這個結構體位于/linux/include/fs.h,代碼如下。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
在file_operations定義了很多I/O操作接口,這里同樣使用了面向對象編程的思想,每個接口可以在重新定義file_operations結構體變量的時候,重新賦于自定義功能的函數,如下,可以理解read,write,open,unlocked_ioctl,release是對抽象函數的實現。
static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, };
1
2
3
4
5
6
7
8
設備注冊過程
設備的初始化在函數cnc_character_init中完成具體的功能實現,主要分為兩個部分,設備號的申請和設備的注冊。其中設備注冊單獨封裝到register_device函數中。
dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); }
1
2
3
4
5
6
7
8
character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0);
1
2
3
4
5
6
7
8
9
10
11
在register_device中,主要用到了cdev提供的函數接口。
cdev_init初始化一個字符型設備并傳入自定義的file_operations類型變量cnc_character_ops。
cdev_add將初始化的字符型設備添加到內核,并分配已經申請好的設備號。
static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如何構建
模塊編譯
使用這個Makefile
KVERS = $(shell uname -r) # Kernel modules obj-m += demo_character.o # Specify flags for the module compilation. EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
1
2
3
4
5
6
7
8
9
10
內核編譯
obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o
1
menuconfig DEMO_DRIVERS tristate "demo drivers" config DEMO_CHARACTER_DRIVER tristate "the most simplest character driver" help character driver endif
1
2
3
4
5
6
7
總結
總體上來說,字符型設備驅動框架還是相對簡單的,通過這次學習加深了對cdev的認識和linux內核源碼中面向對象的設計思想,但是這里還沒有對devfs和sysfs做相應的介紹,后面繼續學習這兩者的區別以及總線驅動模型,總之,加油吧。
參考
https://blog.csdn.net/lucky_liuxiang/article/details/83413946
https://www.cnblogs.com/helloahui/p/3677192.html
https://blog.csdn.net/jk110333/article/details/8563647
Linux
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。