Linux驱动学习(三)--字符设备架构与注册
1.内核如何维护设备号的?
chrdevs指针数组
在内核中有一个重要的全局变量:chrdevs指针数组,位于char_dev.c文件中
chrdevs指针数组的每一个成员指向一个char_device_struct结构体,该结构体中,最重要的变量是cdev指针
cdev结构体
cdev结构体:
该结构体定义位于cdev.h
在cdev中,kobj和owner成员不用太关注,内核会自己填充;dev填充设备号的成员;我们需要关心的最重要的成员是file_operations *ops
file_operations结构体
file_operations结构(位于fs.h)
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 (*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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
常用的函数有:
owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
llseek 函数用于修改文件当前的读写位置
read 函数用于读取设备文件
write 函数用于向设备文件写入(发送)数据
poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写
unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应
compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl
mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制
open 函数用于打开设备文件
release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
cdev之间通过链表串起来
需要我们自己实现的东西有:
- cdev
- read
- write
2.用户层如何找到驱动的?
答案:借助文件系统
mknod命令
创建设备节点命令:
mknod /dev/loh c 237 0
- loh:设备名称
- c:字符设备
- 237:主设备号
- 0:次设备号
输入该命令过后,在内核中会产生一个inode结构体,inode会记录文件的相关信息
inode结构体
inode结构体(位于fs.h)
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 */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
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;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
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;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};
其中比较重要我们需要关心的是设备号成员:
有了这个设备号,我们就可以找到 chrdevs数组成员
3.汇总图
此图参考:Linux字符设备驱动开发(一)
4.字符设备注册
常用相关函数:
- void cdev_init(struct cdev *, const struct file_operations *);
- int cdev_add(struct cdev *, dev_t, unsigned);
- void cdev_del(struct cdev *);
实验
加载模块
mknod创建设备节点
应用程序
驱动代码
/*
*cdev.c
*Original Author: luoyunheng, 2025-02-20
*
* Linux驱动之字符设备注册
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static int major = 222;
static int minor = 0;
static dev_t devno;
static struct cdev dev;
static int hello_open(struct inode *inode, struct file *filep)
{
printk("hello_open()\n");
return 0;
}
static struct file_operations hello_ops =
{
.open = hello_open,
};
static int hello_init(void)
{
int result;
int error;
printk("hello_init\n");
devno = MKDEV(major, minor);
result = register_chrdev_region(devno, 1, "loh");
if (result < 0 ) {
printk("register dev number failed\n");
return result;
}
cdev_init(&dev, &hello_ops);
error = cdev_add(&dev, devno, 1);
if (error < 0) {
printk("cdev_add fail\n");
unregister_chrdev_region(devno, 1);
return error;
}
return 0;
}
static void hello_exit(void)
{
printk("hello_exit\n");
cdev_del(&dev);
unregister_chrdev_region(devno, 1);
return;
}
module_init(hello_init);
module_exit(hello_exit);
应用程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/loh", O_RDWR);
if(fd < 0) {
perror("");
return 0;
}
return 0;
}