哈喽,我是老吴,继续记录我的学习心得。
正文目录:
1. 什么是字符设备驱动?
2. 快速体验字符设备驱动和应用程序 (超简单的 demo)
3. 字符设备在内核里的抽象
3.1 字符设备核心代码概览
3.2 对字符设备进行抽象: struct cdev
3.3 对字符设备的操作进行抽象:struct file_operations
4. 更多值得学习的知识点
5. 相关参考
写作目的:
测试环境:
1.哪些是字符设备驱动?
2.快速体验字符设备驱动和应用程序(超简单的demo)
1)字符设备驱动(chrdev_drv.c):
字符设备的打开和读函数:
static struct cdev chr_dev; // 字符设备抽象
static dev_t ndev; // 设备号
static int chr_open(struct inode *nd, struct file *filp)
{
printk("chr_open, major=%d, minor=%dn", MAJOR(nd->i_rdev), MINOR(nd->i_rdev));
return 0;
}
static ssize_t chr_read(struct file *filp, char __user *u, size_t sz, loff_t *off)
{
printk("In chr_read()n");
return 0;
}
static int chr_release(struct inode *nd, struct file *filp)
{
printk("In chr_release()n");
return 0;
}
文件操作函数集:
struct file_operations chr_ops =
{
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read,
.release = chr_release,
};
模块加载和卸载:
static int demo_init(void)
{
int ret;
cdev_init(&chr_dev, &chr_ops);
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
if(ret < 0)
return ret;
printk("demo_init():major=%d, minor=%dn", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);
if(ret < 0)
return ret;
return 0;
}
static void demo_exit(void)
{
printk("demo_exit...n");
cdev_del(&chr_dev);
unregister_chrdev_region(ndev, 1);
}
2)访问字符设备的应用程序:
int main()
{
int ret;
char buf[32];
int fd = open("/dev/chr_dev", O_RDONLY|O_NDELAY);
if(fd < 0)
{
printf("open file %s failed!n", CHR_DEV_NAME);
return -1;
}
read(fd, buf, 32);
close(fd);
return 0;
}
3)不用完全理解代码的含意,直接看运行疗效:
# 编译驱动程序
$ make KERNELDIR=XXX/linux ARCH=arm CROSS_COMPILE=arm-linux-
# 编译应用程序
$ arm-linux-gcc chrdev_app.c -o chrdev_app
# 加载驱动模块
$ insmod chrdev_drv.ko
demo_init():major=242, minor=0
# 手动创建字符设备文件节点
$ mknod /dev/chr_dev c 242 0
# 运行应用程序
$ ./chrdev_app
chr_open, major=242, minor=0
In chr_read()
In chr_release()
从里面测运行结果可知,应用程序调用chrdev_app.c/open()会造成驱动程序chrdev_drv.c/structfile_operationschr_ops->open()被调用,read操作也是类似。
内核是怎样实现上述功能的?
带着这个困扰来了解字符设备驱动的框架,才不会迷失在内核里各类复杂的代码细节里。
3字符设备在内核里的具象3.1字符设备核心代码概览
在深入阅读各类代码之前,先整体地概览一遍将会涉及到的程序文件,找出核心主干,能有效地防止深陷纷扰的代码细节中。
1)分解Csource文件,fs/char_dev.c(Linux-4.14):
作用:
char_dev.c是字符设备驱动框架的核心实现文件,它坐落fs目录中,说明了字符设备驱动和文件系统是紧密联系在一起的。
内容(以重要性排序):
1>public函数:
// 1. 字符设备子系统初始化
void __init chrdev_init(void)
// 2. struct cdev 管理相关
void chrdev_show(struct seq_file *f, off_t offset)
void cdev_put(struct cdev *p)
void cd_forget(struct inode *inode)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
void cdev_set_parent(struct cdev *p, struct kobject *kobj)
int cdev_device_add(struct cdev *cdev, struct device *dev)
void cdev_device_del(struct cdev *cdev, struct device *dev)
void cdev_del(struct cdev *p)
struct cdev *cdev_alloc(void)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
// 3. 设备号管理相关
__register_chrdev_region(unsigned int major, unsigned int baseminor,
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
int __register_chrdev(unsigned int major, unsigned int baseminor,
void unregister_chrdev_region(dev_t from, unsigned count)
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
2>public变量:
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
3>private变量:
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
static struct kobj_map *cdev_map;
static struct kobj_type ktype_cdev_default;
static struct kobj_type ktype_cdev_dynamic;
4>private函数:
static inline int major_to_index(unsigned major)
static int find_dynamic_major(void)
static struct kobject *cdev_get(struct cdev *p)
static int chrdev_open(struct inode *inode, struct file *filp)
static void cdev_purge(struct cdev *cdev)
static struct kobject *exact_match(dev_t dev, int *part, void *data)
static int exact_lock(dev_t dev, void *data)
static void cdev_unmap(dev_t dev, unsigned count)
static void cdev_default_release(struct kobject *kobj)
static void cdev_dynamic_release(struct kobject *kobj)
static struct kobject *base_probe(dev_t dev, int *part, void *data)
2)分解Cheader文件,include/linux/cdev.h(Linux-4.14):
作用:
包含字符设备驱动相关结构体的定义、以及一些字符设备核心API的申明。
内容:
1>structcdev结构体:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
} __randomize_layout;
3.2对字符设备进行具象:structcdev
编撰字符设备驱动程序就是为了管理和控制字符设备,Linux内核将字符设备具象为一个数据结构:structcdev。这个结构体虽然从Linux-2.6到如今Linux-5.8都没有发生变化。
1)structcdev成员简介:
目前没必要完全理解那些成员的作用,有个大约印象就好:
2)两种方法创建structcdev对象:
这儿用对象一词,是为了引导你们用面向对象的思维来看待Linux内核的设计。
面向过程还是面向对象linux之家,不应当和语言绑定在一起,应当理解为2种不同的编程思维。人脑是很美妙的,在不同的场景,只要你乐意,它能够应用不同的思维方法来解决问题。设计Linux内核代码的神牛们,堪称是各个都是面向对象编程的大牛,练习编程就应当练习对事物的具象能力,C程序员若果认为自己缺少这方面的能力,不如学习一下Java编程。
静态定义:
static struct cdev chr_dev;
动态分配:
struct cdev *my_cdev = cdev_alloc();
cdev_alloc()除了会为structcdev对象分配显存空间深入linux设备驱动程序内核机制,都会对该对象进行必要的初始化:
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
一个值得注意的点:
我搜索了一下内核源码,发觉大多数驱动都选择静态定义structcdev,这样更简单省事。
3)某个真实字符硬件的具象:
数据结构structcdev作为字符设备的具象,仅仅是为了满足Linux内核对字符设备驱动程序框架结构设计的须要。
现实中,一个具体的字符硬件设备的数据结构的具象常常要复杂得多,在这些情况下structcdev经常作为一种内嵌的成员变量出现在实际设备的数据结构中。
比如drvier/watchdog/watchdog_dev.c:
struct watchdog_core_data {
struct kref kref;
struct cdev cdev;
struct watchdog_device *wdd;
struct mutex lock;
unsigned long last_keepalive;
unsigned long last_hw_keepalive;
struct delayed_work work;
unsigned long status; /* Internal status bits */
};
4)初始化cdev对象:
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_init()最重要的作用就是将structcdev对象和structfile_operations对象绑定在一起。
一些值得注意的点:
3.3对字符设备的操作进行具象:structfile_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 (*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 (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
[...];
} __randomize_layout;
鉴于大多数人的注意力难以在一篇文章里上集中太久,更多的内容将置于旁边的文章里。建议你们可以先自行阅读相关书籍,不是自己理解到的东西是消化不了的。
4.更多值得学习的知识点5.相关参考三、思考技术,也思索人生
学习技术,更要学习怎么生活。
你和我各有一个苹果,假如我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个看法,我们交换看法的话,我们就都有两个看法了。
对嵌入式系统(Linux、RTOS、OpenWrt、Android)和开源软件感兴趣,想和更多人相互交流学习深入linux设备驱动程序内核机制,请关注公众号:嵌入式Hacker,一上去学习吧。
无论是关注或转发,还是打赏,都是对作者莫大的支持。认为文章对你有价值的话,不妨点个在看和点赞哦。
欢迎加入我的陌陌群:先加我,我拉你进群,暗号(我最棒)。
祝诸位工作顺利,家庭幸福linux下socket编程,财源滚滚~~~