7、字符设备系统
一、字符设备驱动的结构
1、cdev 结构体
struct cdev {
struct kobject kobj; // 内嵌的Kobject对象
struct module *owner; // 所属的模块
const struct file_operations *ops; // 文件操作结构体
struct list_head list; // 内核维护你的链表
dev_t dev; // 设备号,由主次设备号组成
unsigned int count;
};在这个结构体中,dev_t dev,是设备的设备号,它由32位的,由两个部分,既主设备号和次设备号组成,12位是主设备号,20位次设备号可以用过下面的宏来获得主次设备号:
MAJOR(dev_t dev) // 获得主设备号
MINOR(dev_t dev) // 获得次设备号
如果要将它们合成为一个 dev_t 类型的话:
MKDEV(int major, int minor); //获得设备号
2、cdev 设备的操作
struct cdev*cdev_alloc(void); // 申请内存
void cdev_init(struct cdev *cdev, struct file_operation * fops) // fops 与 cdev 设备绑定
int cdev_add(struct cdev *cdev,dev_t devt, unsigned num); // 字符设备的注册
void cdev_del(struct cdev *cdev) // 注销
cdev_init 函数,建立cdev 与文件操作指针的连接,cdev_add 是将字符设备注册到系统,cdev_add 里面的一个参数是 dev_t ,所以之前必须先获取到设备号。 cdev)_del 注销设备,一般是在字符设备卸载的函数中。
cdev 注册之前,需要获取到设备号:
int register_chrdev_region(dev_t from, unsigned long count, const char * name)
int alloc_chrdev_region(dev_t *dev, unsigned minor, unsigned count, const char * name)
register_chrdev_region 是在已经确定设备号 from 的时候,count 是申请的个数, name :名字,这些名字,最终是会在 /proc/devices 中显示。
alloc_chrdev_region ,一般是使用这个,因为一般都是不知道设备号的,minor: 申请最小的次设备号,count 个数,name 名字。
完成设备号的注册,注销的时候是:
void unregister_chrdev_region(dev_t from,unsigned count);
3、字符设备操作流程
struct cdev *mycdev = NULL; // 1、定义 cdev 设备
static int __init xxx_init(void) // 入口
{
mycdev = cdev_alloc(); // 2、分配地址
cdev_init(mycdev, &fops); // 3、初始化,将文件操作结构体绑定
ret = alloc_cdev_region(&xxx_dev_no, 0, 1 , “mycdev”); // 4、分配设备号
mycdev->owner = THIS_MODULE; //5、其余参数的指定
cdev_add(mycdev, xxx_dev_no, 1); // 6、注册设备;
}
static void __exit xxx_exit(void)
{
unregister_chrdev_region(xxx_dev_no, 1);
cdev_del(mycdev);
}
module_init(xxx_init);
module_exit(xxx_exit);
二、重要的结构体
1、file_operation 结构体
file_operation 的成员函数,是字符设备驱动与内核虚拟文件系统的接口,一般,提供了,read、write、ioctl 等常用函数。
struct file_operations {
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
ssize_t(*read) (struct file *filp, char __user * buf, size_t count, loff_t * f_pos);
file : 结构体,将在后续的位置学习,
__user : 充当了特殊的注释,说明 buf 是用户空间的内存地址,这里需要注意的是,用户空间的地址与内核空间的地址,是不能直接进行读写的
count : 要写入 buf 的字节数,
f_pos : 文件指针的位置,
成功,则返回写入的字节数,失败则返回 EINVAL
ssize_t(*write) (struct file * filp, const char __user * buf, size_t count, loff_t *fpos);
__user : 用户空间的buf
count ; 写入buf 的字节数
fpos : 文件指针
而 用户空间与内核空间的数据,只能通过 copy_to_usr 或者 copy_from_usr 完成内核空间与数据空间的交换,
int (*open) (struct inode *, struct file *);
inode : 结构体指针
file : 指针
2、file 结构体
当打开一个文件的时候,系统会为每个打开的文件,在内核的空间有一个关联的 struct file。她由内核在打开文件的时候,进行创建,当要对这个文件进行其他操作的时候,会将这个结构体 struct file 传递给任何要对这个文件进行操作的函数。file 结构体 里面保存了关于文件的 N 多信息,
struct file {
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 */
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags; // 文件的标志, O_RDONLY,O_NONBLOCK,
fmode_t f_mode; // 文件的读或者写模式, FMODE_READ,FMODE_WRITE
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
};file 结构体里面信息很多,但是常用的较少,打开文件的时候, file 里面填充的信息,我们可以去使用,比如我们可以判定是不是非阻塞的模式:
if(file->flags & O_NONBLOCK)
printf(“NONBLOCK \N”);
else
printf(“BLOCK \N”);
而 file 里面的私有数据 void *private_data,这个指针,一般上指向了保存自定义设备结构体的地址,当read 或者 write 的时候,再从这个私有的指针,获取到自动的设备结构体。
3、 inode 结构体
当文件保存在磁盘的时候,inode 结构体 保存了文件访问的属性、属主、大小、访问时间、生成时间、最后修改时间等信息。她是 Linux管理文件系统的最基本的单位。
struct inode {
umode_t i_mode;
uid_t i_uid;
gid_t i_gid;
struct mutex i_mutex;dev_t i_rdev; // 文件的设备号
struct timespec i_atime; // access 最后的存取时间
struct timespec i_mtime; // mod 最后的修改时间
struct timespec i_ctime; // inode 产生时间,createunion {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; // 字符设备
};上面的这些信息中,对于编写驱动最为有用的是 dev_t i_rdev; 记录的是文件的设备号,struct cdev *i_cdev;,可以通过:
unsigned int imonir(struct inode * inode) // 获取次设备号
unsigned int imajor(struct inode * inode) // 获取主设备号
也可以通过 stat + 文件的命令,查看 一个文件的状态:
[carlos@localhost 3516c]$ stat gpio.ko
File: “gpio.ko”
Size: 7757 Blocks: 16 IO Block: 4096 一般文件
Device: 801h/2049d Inode: 358613086 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 566/ carlos) Gid: ( 566/ carlos)
Access: 2016-05-07 15:29:55.000000000 +0800
Modify: 2016-05-06 13:31:14.000000000 +0800
Change: 2016-05-06 13:31:14.000000000 +0800其实也是获取了 inode 的信息。