Linux驱动开发庖丁解牛之三
——揭开字符设备驱动程序的面纱
http://www.kuqin.com/article/04linux/1047068.html
By:dreamice 2008-11-23
[email][email protected][/email]
1.写在前面的话
我们知道,在Linux设备驱动开发中,包括三大设备类:字符设备,块设备和网络设备。而字符设备,作为最简单的设备类,为此,我们将从最简单的字符设备开始,走进Linux驱动程序设计的神秘殿堂。
——我们已经踏上了真正的设备驱动开发的道路了!
有志者,事竟成。付出越多,而上苍定会以同等的收获回馈于你,当然,最重要的一点是:我们必须走上正确的道路,做正确的付出。开始吧……
参考书目:
《Linux Device Driver》第三版
《Understanding the linux kernel》第三版
《Linux设备驱动开发详解》
2.必备之“砖”
盖大楼,得预先准备好砖头。同样的道理,要写好驱动程序,我们也必须准备好自己的“砖头”,拿好这些砖头,便会真正如庖丁解牛般,游刃于Linux驱动程序设计的神奇艺术之中。
在Linux的设计之初,曾提出:一切皆文件,如果一个东西不是文件,那就是进程。由此可见,文件的概念在Linux系统中可谓是根深蒂固,以至于它深入到对驱动程序的控制,这也是情理之中的事。
下图描述了Linux系统中虚拟文件系统和进程之间的关系:
dreamice 回复于:2008-11-23 22:37:23
图表 1进程和文件系统的关系
在上图中,我们看到了Process,File object,dentry object,inode object以及Sperblock object等概念。Process就是指一个特定的进程,而File obeject对应于进程打开的一个文件;dentry object描述了一个目录项;inode object则对应于磁盘上一个特定的文件;Sperblock object描述了文件系统的相关信息。从这个图中,可以看到进程到磁盘上一个文件实体的路径及对应关系。下面,我们一次看看这些实体结构在内核中的定义。
2.1 File object
File结构代表一个打开的文件,系统中每个打开的文件,在内核空间都对应一个file结构。它由内核在调用open时创建,并传递给在该文件上操作的所有函数,直到最后的close函数。在文件的所有实例都被关闭以后,内核才会释放这个结构。
在内核中,通常以filp来代表指向file结构的指针。File结构的详细定义如下:
//linux/fs.h
779 struct file {
780 /*
781 * fu_list becomes invalid after file_free is called and queued via
782 * fu_rcuhead for RCU freeing
783 */
784 union {
785 struct list_head fu_list;
786 struct rcu_head fu_rcuhead;
787 } f_u;
788 struct path f_path;
789 #define f_dentry f_path.dentry
790 #define f_vfsmnt f_path.mnt
791 const struct file_operations *f_op; //与文件操作相关的函数指针结构
792 atomic_t f_count;
793 unsigned int f_flags;
794 mode_t f_mode;
795 loff_t f_pos;
796 struct fown_struct f_owner;
797 unsigned int f_uid, f_gid;
798 struct file_ra_state f_ra;
799
800 u64 f_version;
801 #ifdef CONFIG_SECURITY
802 void *f_security;
803 #endif
804 /* needed for tty driver, and maybe others */
805 void *private_data;
806
807 #ifdef CONFIG_EPOLL
808 /* Used by fs/eventpoll.c to link all the hooks to this file */
809 struct list_head f_ep_links;
810 spinlock_t f_ep_lock;
811 #endif /* #ifdef CONFIG_EPOLL */
812 struct address_space *f_mapping;
813 };
1166 /*
1167 * NOTE:
1168 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
1169 * can be called without the big kernel lock held in all filesystems.
1170 */
1171 struct file_operations {
1172 struct module *owner;
1173 loff_t (*llseek) (struct file *, loff_t, int);
1174 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1175 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1176 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1177 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1178 int (*readdir) (struct file *, void *, filldir_t);
1179 unsigned int (*poll) (struct file *, struct poll_table_struct *);
1180 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
1181 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1182 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1183 int (*mmap) (struct file *, struct vm_area_struct *);
1184 int (*open) (struct inode *, struct file *);
1185 int (*flush) (struct file *, fl_owner_t id);
1186 int (*release) (struct inode *, struct file *);
1187 int (*fsync) (struct file *, struct dentry *, int datasync);
1188 int (*aio_fsync) (struct kiocb *, int datasync);
1189 int (*fasync) (int, struct file *, int);
1190 int (*lock) (struct file *, int, struct file_lock *);
1191 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1192 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1193 int (*check_flags)(int);
1194 int (*dir_notify)(struct file *filp, unsigned long arg);
1195 int (*flock) (struct file *, int, struct file_lock *);
1196 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1197 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1198 int (*setlease)(struct file *, long, struct file_lock **);
1199 };
其中,蓝色字体标出部分,为与驱动程序最为密切的部分。由于很多书中都对这些结构体做了详细的阐述,这里就不再赘述了。
2.2 inode object
内核用inode结构在内部表示文件,它和file结构的不同之处在于:file表示打开的文件描述符,对单个文件,可能有多个表示打开的文件描述符的file结构,但他们都指向同一个inode结构。
Inode结构的详细定义如下:
593 struct inode {
594 struct hlist_node i_hash;
595 struct list_head i_list;
596 struct list_head i_sb_list;
597 struct list_head i_dentry;
598 unsigned long i_ino;
599 atomic_t i_count;
600 unsigned int i_nlink;
601 uid_t i_uid;
602 gid_t i_gid;
603 dev_t i_rdev;
604 u64 i_version;
605 loff_t i_size;
606 #ifdef __NEED_I_SIZE_ORDERED
607 seqcount_t i_size_seqcount;
608 #endif
609 struct timespec i_atime;
610 struct timespec i_mtime;
611 struct timespec i_ctime;
612 unsigned int i_blkbits;
613 blkcnt_t i_blocks;
614 unsigned short i_bytes;
615 umode_t i_mode;
616 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
617 struct mutex i_mutex;
618 struct rw_semaphore i_alloc_sem;
619 const struct inode_operations *i_op;//inode操作函数集合
620 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
621 struct super_block *i_sb;
622 struct file_lock *i_flock;
623 struct address_space *i_mapping;
624 struct address_space i_data;
625 #ifdef CONFIG_QUOTA
626 struct dquot *i_dquot[MAXQUOTAS];
627 #endif
628 struct list_head i_devices;
629 union {
630 struct pipe_inode_info *i_pipe;
631 struct block_device *i_bdev;
632 struct cdev *i_cdev;
633 };
634 int i_cindex;
635
636 __u32 i_generation;
637
638 #ifdef CONFIG_DNOTIFY
639 unsigned long i_dnotify_mask; /* Directory notify events */
640 struct dnotify_struct *i_dnotify; /* for directory notifications */
641 #endif
642
643 #ifdef CONFIG_INOTIFY
644 struct list_head inotify_watches; /* watches on this inode */
645 struct mutex inotify_mutex; /* protects the watches list */
646 #endif
647
648 unsigned long i_state;
649 unsigned long dirtied_when; /* jiffies of first dirtying */
650
651 unsigned int i_flags;
652
653 atomic_t i_writecount;
654 #ifdef CONFIG_SECURITY
655 void *i_security;
656 #endif
657 void *i_private; /* fs or device private pointer */
658 };
2.3 Super block object
Super block object对应于一个特定的文件系统,通常对应于存放在磁盘扇区中的文件系统超级块或文件系统控制块,而对于非基于文件系统的文件,他们会在使用现场创建超级块,并将其保存到内存中。
一下是结构体的详细描述:
981 struct super_block {
982 struct list_head s_list; /* Keep this first */
983 dev_t s_dev; /* search index; _not_ kdev_t */
984 unsigned long s_blocksize;
985 unsigned char s_blocksize_bits;
986 unsigned char s_dirt;
987 unsigned long long s_maxbytes; /* Max file size */
988 struct file_system_type *s_type;
989 const struct super_operations *s_op;
990 struct dquot_operations *dq_op;
991 struct quotactl_ops *s_qcop;
992 const struct export_operations *s_export_op;
993 unsigned long s_flags;
994 unsigned long s_magic;
995 struct dentry *s_root;
996 struct rw_semaphore s_umount;
997 struct mutex s_lock;
998 int s_count;
999 int s_syncing;
1000 int s_need_sync_fs;
1001 atomic_t s_active;
1002 #ifdef CONFIG_SECURITY
1003 void *s_security;
1004 #endif
1005 struct xattr_handler **s_xattr;
1006
1007 struct list_head s_inodes; /* all inodes */
1008 struct list_head s_dirty; /* dirty inodes */
1009 struct list_head s_io; /* parked for writeback */
1010 struct list_head s_more_io; /* parked for more writeback */
1011 struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */
1012 struct list_head s_files;
1013
1014 struct block_device *s_bdev;
1015 struct mtd_info *s_mtd;
1016 struct list_head s_instances;
1017 struct quota_info s_dquot; /* Diskquota specific options */
1018
1019 int s_frozen;
1020 wait_queue_head_t s_wait_unfrozen;
1021
1022 char s_id[32]; /* Informational name */
1023
1024 void *s_fs_info; /* Filesystem private info */
1025
1026 /*
1027 * The next field is for VFS *only*. No filesystems have any business
1028 * even looking at it. You had been warned.
1029 */
1030 struct mutex s_vfs_rename_mutex; /* Kludge */
1031
1032 /* Granularity of c/m/atime in ns.
1033 Cannot be worse than a second */
1034 u32 s_time_gran;
1035
1036 /*
1037 * Filesystem subtype. If non-empty the filesystem type field
1038 * in /proc/mounts will be "type.subtype"
1039 */
1040 char *s_subtype;
1041
1042 /*
1043 * Saved mount options for lazy filesystems using
1044 * generic_show_options()
1045 */
1046 char *s_options;
1047 };
2.4 Identry object
Linux中把目录也当作文件,为了方便查找操作,虚拟文件系统(VFS)引入了目录项的概念。每个dentry代表路径中一个特定部分。由于驱动程序很少涉及到dentry,所以在这里就不做描述了。
3. 实例剖析Linux字符设备驱动程序
Linux的变化真的是太快了。当我们还在研读最新版的LDD3(基于内核2.6.11)时,而实际上,它的驱动程序框架结构已经发生了很大的变化。当我还在投入的学习scull示例驱动程序的时候,我发现,对于编写一个字符设备驱动程序,已经有了新的变化。原本打算剖析scull程序,来达到融会贯通的目的,然而,面对变化,我不得不去学习一种全新的字符设备驱动程序的编写。
我想这是一种挑战,学习,是一个终身的过程。
3.1 描述字符设备体结构(cdev)
在新版的linux内核中,使用cdev结构体描述一个字符设备,其定义如下:
//include/linux/cdev.h
13 struct cdev {
14 struct kobject kobj;//kobject对象
15 struct module *owner;
16 const struct file_operations *ops;//文件操作结构体
17 struct list_head list;
18 dev_t dev;//定设备的主次设备号
19 unsigned int count;
20 };
(关于kobject还正在研究中……)
结构中的ops定义了操作File 的函数指针集,dev存储了主次设备号。注意,在很多著作中谈到Linux2.6内核中,dev_t是一个32位的类型,其中高12位存储主设备号,低20位存储次设备号。但我们最好不要这样去引用,内核版本的变化可谓是瞬息万变,很可能有一天这个约定就变化了,因此就将导致旧的驱动程序将不再兼容新的内核。最好的方式是使用下面两个宏定义获取主次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
使用下面的宏则可以将主次设备号生成一个dev_t:
MKDEV(int major, int minor)
一下是操作cdev结构体的相关内核函数:
22 void cdev_init(struct cdev *, const struct file_operations *); //初始化cdev结构,并建立//cdev结构和file_operations之间的连接
23
24 struct cdev *cdev_alloc(void);//动态申请cdev内存
25
26 void cdev_put(struct cdev *p);//解除读一个cdev的引用,相应的cdev_get函数增加
//对cdev的引用计数
27
28 int cdev_add(struct cdev *, dev_t, unsigned);//向系统添加一个cdev
29
30 void cdev_del(struct cdev *);//删除系统中一个cdev
31
32 void cd_forget(struct inode *);//
3.2 分配和释放设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
该函数用于已知起始设备的设备号的情况。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
对设备号未知的情况,使用该函数向内核动态申请未被占用的设备号。
对于一个驱动程序员来说,如果不确定设备号,最好不要妄自定义一个设备号进行注册,这样的后果是:如果该设备号已用,将注册不成功;如果该设备号在后来将被引用,那么将导致其他设备无法注册。所以,在这种情况下,最好使用动态注册的方式。
void unregister_chrdev_region(dev_t from, unsigned count)
该函数释放先前注册的设备。
3.3 一个简单的字符设备驱动程序
global_mem.c
1 /*=============================================================
2 A simple example of char device drivers
3
4
5 <[email][email protected][/email]>
6 ============================================================*/
7 #include <linux/module.h>
8 #include <linux/types.h>
9 #include <linux/fs.h>
10 #include <linux/errno.h>
11 #include <linux/mm.h>
12 #include <linux/sched.h>
13 #include <linux/init.h>
14 #include <linux/cdev.h>
15 #include <asm/io.h>
16 #include <asm/system.h>
17 #include <asm/uaccess.h>
18
19 #define GLOBALMEM_SIZE 0x1000 /* 虚拟字符设备内存缓冲区大小 */
20 #define MEM_CLEAR 0x1 /* ioctl操作指令 (这里只是简单起见,不建议这么定义)*/
21 //#define GLOBALMEM_MAJOR 254 /* 静态定义主设备号 */
22 #define GLOBALMEM_MAJOR 0 /*定义动态申请主设备号*/
23
24 static int globalmem_major = GLOBALMEM_MAJOR;
25 /*globalmem 虚拟字符设备结构体 */
26 struct globalmem_dev
27 {
28 struct cdev cdev; /*cdev结构体 */
29 unsigned char mem[GLOBALMEM_SIZE]; /*虚拟设备内存区大小*/
30 };
31
32 struct globalmem_dev *globalmem_devp;
34 int globalmem_open(struct inode *inode, struct file *filp)
35 {
36 /*将设备结构体指针赋给文件私有数据 */
37 filp->private_data = globalmem_devp;
38 return 0;
39 }
40 /*释放函数*/
41 int globalmem_release(struct inode *inode, struct file *filp)
42 {
43 return 0;
44 }
45
46 /* ioct操作函数*/
47 static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
48 int cmd, unsigned long arg)
49 {
50 struct globalmem_dev *dev = filp->private_data;/*»ñµÃÉ豸½á¹¹ÌåÖ¸Õë*/
51
52 switch (cmd)
53 {
54 case MEM_CLEAR:
55 memset(dev->mem, 0, GLOBALMEM_SIZE);
56 printk(KERN_INFO "globalmem is set to zero\n");
57 break;
58
59 default:
60 return - EINVAL;
61 }
62 return 0;
63 }
64
65 /*read函数*/
66 static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
67 loff_t *ppos)
68 {
69 unsigned long p = *ppos;
70 unsigned int count = size;
71 int ret = 0;
72 struct globalmem_dev *dev = filp->private_data;
73
74
75 if (p >= GLOBALMEM_SIZE)
76 return count ? - ENXIO: 0;
77 if (count > GLOBALMEM_SIZE - p)
78 count = GLOBALMEM_SIZE - p;
79
80
81 if (copy_to_user(buf, (void*)(dev->mem + p), count))
82 {
83 ret = - EFAULT;
84 }
85 else
86 {
87 *ppos += count;
88 ret = count;
89
90 printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
91 }
92
93 return ret;
94 }
95
96 /*дº¯Êý*/
97 static ssize_t globalmem_write(struct file *filp, const char __user *buf,
98 size_t size, loff_t *ppos)
99 {
100 unsigned long p = *ppos;
101 unsigned int count = size;
102 int ret = 0;
103 struct globalmem_dev *dev = filp->private_data;
104
105
106 if (p >= GLOBALMEM_SIZE)
107 return count ? - ENXIO: 0;
108 if (count > GLOBALMEM_SIZE - p)
109 count = GLOBALMEM_SIZE - p;
110
111 //
112 if (copy_from_user(dev->mem + p, buf, count))
113 ret = - EFAULT;
114 else
115 {
116 *ppos += count;
117 ret = count;
118
119 printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
120 }
121
122 return ret;
123 }
124
125
126 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
127 {
128 loff_t ret = 0;
129 switch (orig)
130 {
131 case 0: //起始位置
132 if (offset < 0)
133 {
134 ret = - EINVAL;
135 break;
136 }
137 if ((unsigned int)offset > GLOBALMEM_SIZE)
138 {
139 ret = - EINVAL;
140 break;
141 }
142 filp->f_pos = (unsigned int)offset;
143 ret = filp->f_pos;
144 break;
145 case 1: /*当前位置*/
146 if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
147 {
148 ret = - EINVAL;
149 break;
150 }
151 if ((filp->f_pos + offset) < 0)
152 {
153 ret = - EINVAL;
154 break;
155 }
156 filp->f_pos += offset;
157 ret = filp->f_pos;
158 break;
159 default:
160 ret = - EINVAL;
161 break;
162 }
163 return ret;
164 }
165
166 /*操作函数结构体*/
167 static const struct file_operations globalmem_fops =
168 {
169 .owner = THIS_MODULE,
170 .llseek = globalmem_llseek,
171 .read = globalmem_read,
172 .write = globalmem_write,
173 .ioctl = globalmem_ioctl,
174 .open = globalmem_open,
175 .release = globalmem_release,
176 };
177
178 /*初始化并注册cdev */
179 static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
180 {
181 int err, devno = MKDEV(globalmem_major, index);
182
183 cdev_init(&dev->cdev, &globalmem_fops);
184 dev->cdev.owner = THIS_MODULE;
185 dev->cdev.ops = &globalmem_fops;
186 err = cdev_add(&dev->cdev, devno, 1);
187 if (err)
188 printk(KERN_NOTICE "Error %d adding LED%d", err, index);
189 }
190
191 /*设备驱动模块加载*/
192 int globalmem_init(void)
193 {
194 int result;
195 dev_t devno = MKDEV(globalmem_major, 0);
196
197 /*静态指定设备编号*/
198 if (globalmem_major)
199 result = register_chrdev_region(devno, 1, "globalmem");
200 else /*动态申请*/
201 {
202 result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
203 globalmem_major = MAJOR(devno);
204 }
205 if (result < 0)
206 return result;
207
208 /* 动态申请设备结构体内存*/
209 globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
210 if (!globalmem_devp) /*ÉêÇëʧ°Ü*/
211 {
212 result = - ENOMEM;
213 goto fail_malloc;
214 }
215 memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
216
217 globalmem_setup_cdev(globalmem_devp, 0);
218 printk(KERN_INFO"Init global_mem success!\n");
219 return 0;
220
221 fail_malloc: unregister_chrdev_region(devno, 1);
222 return result;
223 }
224
225 /*模块卸载函数*/
226 void globalmem_exit(void)
227 {
228 cdev_del(&globalmem_devp->cdev); /*注销cdev*/
229 kfree(globalmem_devp); /*释放结构体内存*/
230 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
231 printk(KERN_INFO"Bye-bye global_mem!\n");
232 }
233
234 MODULE_AUTHOR("Dreamice");
235 MODULE_LICENSE("Dual BSD/GPL");
236
237 module_param(globalmem_major, int, S_IRUGO);
238
239 module_init(globalmem_init);
240 module_exit(globalmem_exit);
Makefile:
1 TARGET = global_mem
2 KDIR = /lib/modules/$(shell uname -r)/build
3 PWD = $(shell pwd)
4 obj-m := $(TARGET).o
5 default:
6 make -C $(KDIR) M=$(PWD) modules
7 clean:
8 $(RM) *.o *.ko *.mod.c Module.symvers modules.order
测试:
# cd /sys/module/globalmem/parameters
# cat globalmem_major
251
# mknod /dev/globalmem c 251 0
# echo ‘hello dreamice’ > /dev/gloablmem
# cat /dev/gloablmem
hello dreamice
当然,我们也可以写c程序来测试该程序。
4.总结:
在编写字符设备驱动程序中,最重要的是file_operations这个结构,我们通常只需要实现read,write,ioctl等函数的操作。
对于一个特定的字符设备驱动的编写,必须把该设备同一个cdev结构关联起来。我们必须完成设备号的指定或者动态申请,这是设备在系统中的唯一标识。然后,完成设备驱动程序的模块初始化,以及模块注销的相关实现。
注意,file_operations与cdev结构的关联,这与老版本的驱动程序编写有很大差异。
5.后续工作
(1)研究总结kobject以及sysfs;
(2)深入学习,通过改进该实例驱动程序,使之包含更多模块及设备驱动程序编写涉及的知识点,如多个子设备,内核同步相关知识点等,来达到更深入领会字符设备驱动程序的目的。
(3)深入剖析内核文件系统。