首先需要注册设备号,有两个函数可以实现该功能:
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);
MAJOR(dev_t dev_id); MINOR(dev_t dev_id);将主设备号和次设备号转换为dev_t类型,则可以使用下面的宏:
MKDEV(int major, int minor);其中,major为主设备号,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))在分配好了设备号之后需要初始化设备结构体,使用函数
void cdev_init(struct cdev *cdev, const struct file_operations *fops);该函数带两参数,先看一下其结构体:
struct cdev { struct kobject kobj; struct module *owner; //所属模块 const struct file_operations *ops;//文件操作结构体 struct list_head list; dev_t dev; //设备号 unsigned int count; }; 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 (*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 cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops;//这一行就是将传入的第二个参数赋值给cdev的ops。 }所以在调用该函数之前,还需要初始化file_operations结构体。一般使用C标记式结构初始化语法,如下:
static const struct file_operations xxx_fops = { .owner = THIS_MODULE, .llseek = xxx_llseek, .read = xxx_read, .write = xxx_write, .ioctl = xxx_ioctl, .open = xxx_open, .release = xxx_release, };之后再定义该设备结构体所有者:
cdev.owner = THIS_MODULE;
int cdev_add(struct cdev *p, dev_t dev, unsigned count);参数p为将要添加的设备结构体,dev为该设备要求的第一个设备号,count为关联到设备的设备号数目,常常为1。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { p->dev = dev; p->count = count; return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); }
void cdev_del(struct cdev *dev);还应该释放原先申请的设备号:使用
void unregister_chrdev_region(dev_t from, unsigned count);这两函数的调用通常发生在驱动模块卸载函数中。
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #define DEV_NAME "xxx_cdev" int xxx_major = 0; dev_t xxx_devno; struct xxx_cdev_t { struct cdev cdev; } xxx_dev; int xxx_open(struct inode *inode, struct file *filep) { return 0; } static const struct file_operations xxx_fops = { .owner = THIS_MODULE, .open = xxx_open, }; static int __init xxx_init(void) { int ret; if (xxx_major) { xxx_devno = MKDEV(xxx_major, 0); register_chrdev_region(xxx_devno, 1, DEV_NAME); } else { alloc_chrdev_region(&xxx_devno, 0, 1, DEV_NAME); } cdev_init(&xxx_dev.cdev, &xxx_fops); xxx_dev.cdev.owner = THIS_MODULE; if ((ret = cdev_add(&xxx_dev.cdev, xxx_devno, 1) < 0)) { unregister_chrdev_region(xxx_devno, 1); return ret; } return 0; } static void __exit xxx_exit(void) { unregister_chrdev_region(xxx_devno, 1); cdev_del(&xxx_dev.cdev); } module_init(xxx_init); module_exit(xxx_exit); MODULE_LICENSE("GPL");下面来讲一讲file_operations结构体成员函数的实现,因为这些函数是字符设备和内核接口,用户态应系统调用的最终实施者,实现字符设备驱动主要就是实现这些函数。一般read,write,ioctl这些函数一般都会实现。
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; spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */ atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 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; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; #ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state; #endif };其中private_data 是一个有用的资源, 在系统调用间保留状态信息,一般在open中设置它。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);读写函数第一个参数为文件结构体指针,第二个参数是用户空间的内存地址,在内核空间不能直接读写,第三个参数是要读写的文件字节数,第四个参数是读写位置相对于文件开头的位置。
unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count); unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);函数返回不能复制的字节数,完全复制返回0。
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);第三个参数为命令,第四个参数为要传入的参数。