Linux驱动开发2——字符设备驱动
1、申请设备号
#include <linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char *name); 静态分配字符设备号,从fist开始的count个,name为设备名称(name会出现在/proc/devices和sysfs中),成功返回0,失败返回一个负的错误码 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 动态分配字符设备号,主设备号动态分配,次设备号从firstminor开始的count个,name为设备名称(动态分配的主设备号可以在/proc/devices中获取) void unregister_chrdev_region(dev_t first, unsigned int count); 注销字符设备号,从first开始的count个
2、初始化字符设备
#include <linux/cdev.h> 动态创建 struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops; 静态创建 void cdev_init(struct cdev *cdev, struct file_operations *fops);
3、添加和删除字符设备
#include <linux/cdev.h> 添加字符设备 int cdev_add(struct cdev *dev, dev_t num, unsigned int count); 删除字符设备 void cdev_del(struct cdev *dev);
4、关键数据结构
4.1、file_operations结构体
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 };
4.2、file结构体
struct file { union { struct llist_node fu_llist; struct rcu_head fu_rcuhead; } f_u; struct path f_path; struct inode *f_inode; /* cached value */ const struct file_operations *f_op; /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock; 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; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
4.3、inode结构体
struct inode { umode_t i_mode; unsigned short i_opflags; kuid_t i_uid; kgid_t i_gid; unsigned int i_flags; #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl; struct posix_acl *i_default_acl; #endif const struct inode_operations *i_op; struct super_block *i_sb; struct address_space *i_mapping; #ifdef CONFIG_SECURITY void *i_security; #endif /* Stat data, not accessed from path walking */ unsigned long i_ino; /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ union { const unsigned int i_nlink; unsigned int __i_nlink; }; dev_t i_rdev; loff_t i_size; struct timespec i_atime; struct timespec i_mtime; struct timespec i_ctime; spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ unsigned short i_bytes; unsigned int i_blkbits; blkcnt_t i_blocks; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif /* Misc */ unsigned long i_state; struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned long dirtied_time_when; struct hlist_node i_hash; struct list_head i_io_list; /* backing dev IO list */ #ifdef CONFIG_CGROUP_WRITEBACK struct bdi_writeback *i_wb; /* the associated cgroup wb */ /* foreign inode detection, see wbc_detach_inode() */ int i_wb_frn_winner; u16 i_wb_frn_avg_time; u16 i_wb_frn_history; #endif struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; union { struct hlist_head i_dentry; struct rcu_head i_rcu; }; u64 i_version; atomic_t i_count; atomic_t i_dio_count; atomic_t i_writecount; #ifdef CONFIG_IMA atomic_t i_readcount; /* struct files open RO */ #endif const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ struct file_lock_context *i_flctx; struct address_space i_data; struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; struct cdev *i_cdev; char *i_link; }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif void *i_private; /* fs or device private pointer */ };
Linux中一切皆文件,对于字符设备驱动而言,都是通过设备文件进行交互的。Linux内核中,针对每一个文件有一个唯一的inode结构体描述,但是一个文件可以打开多次,所以内核使用file结构体作为文件描述符,每次打开同一个文件,均生成一个新的文件描述符,但是所有的文件描述符都指向同一个inode,表示打开的是同一个文件。inode和file结构体中均有file_operation结构体成员,它就是指向驱动实现的file_operations结构体,为文件操作提供具体实现函数。
5、container_of宏
#include <linux/kernel.h>
container_of(pointer, container_type, container_field);
6、open和release
6.1、int open(struct inode *inode, struct file *filp);
open函数中应当进行以下工作:
1)检查设备特定的错误(如设备未准备好,或者硬件错误)
2)如果第一次打开,初始化设备
3)如果需要,更新f_op指针
4)分配并填充要放入filp_private_data的任何数据结构
int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* contain cdev member */ dev = contailer_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; return 0; }
6.2、int release(struct inode *inode, struct file *filp);
release函数应当进行以下工作:
1)释放open分配在filp->private_data中的任何东西
2)在最后close关闭设备
int scull_release(struct inode *inode, struct file *filp) { return 0; }
7、read和write
#include <linux/fs.h> ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp); ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
8、ioctl
#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg); cmd - 操作码(四字节,type|nr|direction|size)
类型:魔数,通常为一个字符,表示特定设备,自定义
序号:命令序号
方向:__IOC_NONE|__IOC_READ|__IOC_WRITE
大小:数据结构大小
根据方向,可以使用_IO(type,nr)
_IOR(type,nr,datatype)
_IOW(type,nr,datatype)
组合生成cmd操作码,size成员通过sizeof(datatype)获取
反之,可以通过宏解析出cmd操作码的不同字段:
类型:_IOC_TYPE(cmd)
方向:_IOC_DIR(cmd)
序号:_IOC_NR(cmd)
大小:_IOC_SIZE(cmd)
arg - 可选参数(可以是一个整数或者一个指针)
返回值:失败返回-ENOTTY或者-ENIVAL给用户
ioctl函数注册时,需要注意按照以下形式注册:
struct file_operations globalvar_fops = {
.owner = THIS_MODULE,
.open = globalvar_open,
.release = globalvar_release,
.read = globalvar_read,
.write = globalvar_write,
.unlocked_ioctl = globalvar_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = globalvar_ioctl,
#endif
};
9、access_ok()
#include <asm/uaccess.h> 检查用户空间地址是否可以存取,成功返回1,失败返回0,失败时,应该给调用者返回-EFAULT错误码 int access_ok(int type, const void *addr, unsigned long size); type: VERIFY_READ|VERIFY_WRITE addr: 用户空间地址 size: 大小