邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛
分类: 嵌入式
2015-06-26 15:56:26
对字符设备访问是通过文件系统内的设备名称进行的。
查看/dev目录,可以看到很多文件:
drwxr-xr-x 2 root root 60 2015-06-25 12:33 net
drwxr-xr-x 2 root root 60(表示文件的大小) 2015-06-25 12:32 pktcdvd (目录文件)
crw-r----- 1 root kmem 1(主设备号), 4(次设备号) 2015-06-25 12:32 port
crw------- 1 root root 108, 0 2015-06-25 12:32 ppp
crw-rw---- 1 root root 10, 1 2015-06-25 12:32 psaux
crw-rw-rw- 1 root tty 5, 2 2015-06-25 18:30 ptmx
lrwxrwxrwx 1 root root 4 2015-06-25 12:32 rtc -> rtc0 (链接文件,创建了符号链接)
crw-rw---- 1 root root 254, 0 2015-06-25 12:32 rtc0
lrwxrwxrwx 1 root root 3 2015-06-25 12:32 scd0 -> sr0
brw-rw---- 1 root disk 8, 0 2015-06-25 12:32 sda
brw-rw---- 1 root disk 8, 1 2015-06-25 12:33 sda1
其中:
d 开头的是普通的目录文件(directory)字符设备含有主设备号,次设备号;其中主设备号用来标识设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备,内核本身不关心次设备号的任何其他信息。
c 开头的是字符设备文件
b 开头的块设备文件
l 开头的是链接文件
dev_t 用来保存设备编号:包括主设备号和次设备号;dev_t类型在<linux/types.h>中定义;
dev_t是一个32位的数,其中12位用来表示主设备号,而其余20位用来表示次设备号。
获取主、次设备号:
MAJOR(dev_t dev);获取设备编号:
MINOR(dev_t dev);
MKDEV(int major, int minor);
- #define MINORBITS 20
- #define MINORMASK ((1U << MINORBITS) - 1)
- #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
- #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
- #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
分配设备编号:
1. 静态分配设备编号
函数:int register_chrdev_region(dev_t from, unsigned count, const char *name)
- /**
- * register_chrdev_region() - register a range of device numbers (注册某个范围的设备编号)
- * @from: the first in the desired range of device numbers; must include (指定范围起始的编号值)
- * the major number.
- * @count: the number of consecutive device numbers required (指定申请连续设备编号的个数大小)
- * @name: the name of the device or driver. (设备或驱动的名字)
- *
- * Return value is zero on success, a negative error code on failure. (注册成功,则返回0;失败,则返回负数)
- */
- int register_chrdev_region(dev_t from, unsigned count, const char *name)
2. 自动分配设备编号
函数:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
- /**
- * alloc_chrdev_region() - register a range of char device numbers
- * @dev: output parameter for first assigned number --------------------->(输出设备编号,保存在这个变量里面)
- * @baseminor: first of the requested range of minor numbers -------------->(请求使用的第一个次设备号)
- * @count: the number of minor numbers required
- * @name: the name of the associated device or driver
- *
- * Allocates a range of char device numbers. The major number will be (动态分配设备编号,保存在第一个参数里面)
- * chosen dynamically, and returned (along with the first minor number) (分配成功,返回0;失败,返回负数)
- * in @dev. Returns zero or a negative error code.
- */
- int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
- const char *name)
释放设备编号:
一般在模块的清除函数中,调用该函数来释放设备。
函数:void unregister_chrdev_region(dev_t from, unsigned count)
- /**
- * unregister_chrdev_region() - return a range of device numbers
- * @from: the first in the range of numbers to unregister (该范围内起始的设备编号)
- * @count: the number of device numbers to unregister (设备的个数)
- *
- * This function will unregister a range of @count device numbers,
- * starting with @from. The caller should normally be the one who
- * allocated those numbers in the first place...
- */
- void unregister_chrdev_region(dev_t from, unsigned count)
动态分配主设备号
对于一个新的驱动程序,不要随便选择一个当前未使用的设备号作为主设备号,而应该使用动态分配机制获取主设备号。
动态分配的缺点:由于不能保证动态分配的主设备号始终一致,所以无法预先创建设备节点。但是可以可以从 /proc/devices 中读取得到。
在scull文件中有main.c文件,里面有获取主设备号的代码:
- /*
- * Get a range of minor numbers to work with, asking for a dynamic
- * major unless directed otherwise at load time.
- */
- if (scull_major) { // 如果major已经指定,则静态分配设备编号
- dev = MKDEV(scull_major, scull_minor);
- result = register_chrdev_region(dev, scull_nr_devs, "scull");
- } else { // 否则动态分配设备编号
- result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
- "scull");
- scull_major = MAJOR(dev);
- }
- if (result < 0) { // 判断返回值,看是否分配成功:为0,表示成功;负值,表示失败。
- printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
- return result;
- }
scull代码,简单测试步骤:
1. 编译,生成模块文件scull.ko
book@book-desktop:/work/ldd/examples/scull$ make
make -C /lib/modules/2.6.31-14-generic/build M=/work/ldd/examples/scull LDDINC=/work/ldd/examples/scull/../include modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.31-14-generic'
CC [M] /work/ldd/examples/scull/main.o
CC [M] /work/ldd/examples/scull/pipe.o
CC [M] /work/ldd/examples/scull/access.o
LD [M] /work/ldd/examples/scull/scull.o
Building modules, stage 2.
MODPOST 1 modules
CC /work/ldd/examples/scull/scull.mod.o
LD [M] /work/ldd/examples/scull/scull.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.31-14-generic'
2. 运行脚本文件,加载模块,生成字符节点文件,可以在/dev下面查看 (注意:运行scull_load可能需要权限,用chmod改变权限先。)
book@book-desktop:/work/ldd/examples/scull$ sudo ./scull_load
book@book-desktop:/work/ldd/examples/scull$ ls -l /dev/scull*
lrwxrwxrwx 1 root root 6 2015-06-25 22:28 /dev/scull -> scull0
crw-rw-r-- 1 root staff 251, 0 2015-06-25 22:28 /dev/scull0
crw-rw-r-- 1 root staff 251, 1 2015-06-25 22:28 /dev/scull1
crw-rw-r-- 1 root staff 251, 2 2015-06-25 22:28 /dev/scull2
crw-rw-r-- 1 root staff 251, 3 2015-06-25 22:28 /dev/scull3
lrwxrwxrwx 1 root root 10 2015-06-25 22:28 /dev/scullpipe -> scullpipe0
crw-rw-r-- 1 root staff 251, 4 2015-06-25 22:28 /dev/scullpipe0
crw-rw-r-- 1 root staff 251, 5 2015-06-25 22:28 /dev/scullpipe1
crw-rw-r-- 1 root staff 251, 6 2015-06-25 22:28 /dev/scullpipe2
crw-rw-r-- 1 root staff 251, 7 2015-06-25 22:28 /dev/scullpipe3
crw-rw-r-- 1 root staff 251, 11 2015-06-25 22:28 /dev/scullpriv
crw-rw-r-- 1 root staff 251, 8 2015-06-25 22:28 /dev/scullsingle
crw-rw-r-- 1 root staff 251, 9 2015-06-25 22:28 /dev/sculluid
crw-rw-r-- 1 root staff 251, 10 2015-06-25 22:28 /dev/scullwuid
3. 查看/proc/devices文件下面是否注册成功
book@book-desktop:/work/ldd/examples/scull$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
251 scull
251 scullp
251 sculla
252 hidraw
253 usbmon
254 rtc
Block devices:
1 ramdisk
2 fd
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 pktcdvd
254 mdp
4. 运行scull_unload文件,卸载模块,并查看:找不到设备节点和设备文件了。
book@book-desktop:/work/ldd/examples/scull$ sudo ./scull_unload
book@book-desktop:/work/ldd/examples/scull$ ls -l /dev/scull*
ls: cannot access /dev/scull*: No such file or directory
book@book-desktop:/work/ldd/examples/scull$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
6 lp
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
252 hidraw
253 usbmon
254 rtc
Block devices:
1 ramdisk
2 fd
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 pktcdvd
254 mdp
最最重要的概念来了。
2.1 重要的数据结构
三个重要的内核数据结构,定义在<linux/fs.h>
struct file_operations
struct file
struct inode
这里只贴出来定义:
struct file_operations:
- /*
- * NOTE:
- * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
- * can be called without the big kernel lock held in all filesystems.
- */
- 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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
- ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
- int (*readdir) (struct file *, void *, filldir_t);
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
- int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
- 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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
- 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 (*dir_notify)(struct file *filp, unsigned long arg);
- 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);
- };
struct file
- struct file {
- /*
- * fu_list becomes invalid after file_free is called and queued via
- * fu_rcuhead for RCU freeing
- */
- union {
- struct list_head fu_list;
- struct rcu_head fu_rcuhead;
- } f_u;
- struct path f_path;
- #define f_dentry f_path.dentry
- #define f_vfsmnt f_path.mnt
- const struct file_operations *f_op;
- atomic_t f_count;
- unsigned int f_flags;
- mode_t f_mode;
- loff_t f_pos;
- struct fown_struct f_owner;
- unsigned int f_uid, f_gid;
- struct file_ra_state f_ra;
- unsigned long f_version;
- #ifdef CONFIG_SECURITY
- void *f_security;
- #endif
- /* needed for tty driver, and maybe others */
- void *private_data;
- #ifdef CONFIG_EPOLL
- /* Used by fs/eventpoll.c to link all the hooks to this file */
- struct list_head f_ep_links;
- spinlock_t f_ep_lock;
- #endif /* #ifdef CONFIG_EPOLL */
- struct address_space *f_mapping;
- };
struct inode:
- struct inode {
- struct hlist_node i_hash;
- struct list_head i_list;
- struct list_head i_sb_list;
- struct list_head i_dentry;
- unsigned long i_ino;
- atomic_t i_count;
- unsigned int i_nlink;
- uid_t i_uid;
- gid_t i_gid;
- dev_t i_rdev;
- unsigned long i_version;
- loff_t i_size;
- #ifdef __NEED_I_SIZE_ORDERED
- seqcount_t i_size_seqcount;
- #endif
- struct timespec i_atime;
- struct timespec i_mtime;
- struct timespec i_ctime;
- unsigned int i_blkbits;
- blkcnt_t i_blocks;
- unsigned short i_bytes;
- umode_t i_mode;
- spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
- struct mutex i_mutex;
- struct rw_semaphore i_alloc_sem;
- const struct inode_operations *i_op;
- const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
- struct super_block *i_sb;
- struct file_lock *i_flock;
- struct address_space *i_mapping;
- struct address_space i_data;
- #ifdef CONFIG_QUOTA
- struct dquot *i_dquot[MAXQUOTAS];
- #endif
- struct list_head i_devices;
- union {
- struct pipe_inode_info *i_pipe;
- struct block_device *i_bdev;
- struct cdev *i_cdev;
- };
- int i_cindex;
- __u32 i_generation;
- #ifdef CONFIG_DNOTIFY
- unsigned long i_dnotify_mask; /* Directory notify events */
- struct dnotify_struct *i_dnotify; /* for directory notifications */
- #endif
- #ifdef CONFIG_INOTIFY
- struct list_head inotify_watches; /* watches on this inode */
- struct mutex inotify_mutex; /* protects the watches list */
- #endif
- unsigned long i_state;
- unsigned long dirtied_when; /* jiffies of first dirtying */
- unsigned int i_flags;
- atomic_t i_writecount;
- #ifdef CONFIG_SECURITY
- void *i_security;
- #endif
- void *i_private; /* fs or device private pointer */
- };
2.2 字符设备的注册和移除
内核用struct cdev结构来表示字符设备,在内核调用设备的操作之前,必须分配并注册一个或者多个cdev结构。
步骤:
- static struct char_device_struct {
- struct char_device_struct *next; // 链表指针
- unsigned int major;
- unsigned int baseminor;
- int minorct;
- char name[64];
- struct file_operations *fops; // file_operations结构体,指定操作函数
- struct cdev *cdev; /* will die */
- } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
1. 先分配并注册设备结构体struct cdev
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); // scull文件中的main.c2. 初始化这个结构体
void cdev_init(struct cdev *, const struct file_operations *);
3. 将这个结构体告诉内核
int cdev_add(struct cdev *, dev_t, unsigned);
例子:scull目录中的main.c
- /*
- * Set up the char_dev structure for this device.
- */
- static void scull_setup_cdev(struct scull_dev *dev, int index)
- {
- int err, devno = MKDEV(scull_major, scull_minor + index);
- cdev_init(&dev->cdev, &scull_fops); // 初始化设备结构体struct scull_dev,与scull_fops进行联系
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &scull_fops; // 多余的,在cdev_init(&dev->cdev, &scull_fops)函数中已经做了这一步
- err = cdev_add (&dev->cdev, devno, 1); // 注册结构体到内核
- /* Fail gracefully if need be */
- if (err)
- printk(KERN_NOTICE "Error %d adding scull%d", err, index);
- }
字符设备的移除,可以调用函数:
- /**
- * cdev_del() - remove a cdev from the system
- * @p: the cdev structure to be removed
- *
- * cdev_del() removes @p from the system, possibly freeing the structure
- * itself.
- */
- void cdev_del(struct cdev *p)
- {
- cdev_unmap(p->dev, p->count);
- kobject_put(&p->kobj);
- }