linux内核源码分析之设备驱动

wolfgang@meitner> ls -l /dev/sd{a,b} /dev/ttyS{0,1} brw-r-----1 root disk 8, 0 2008-02-21 21:06 /dev/sda brw-r-----1 root disk 8, 16 2008-02-21 21:06 /dev/sdb crw-rw----1 root uucp 4, 64 2007-09-21 21:12 ttyS0 crw-rw----1 root uucp 4, 65 2007-09-21 21:12 ttyS1
  • 访问权限前的字母是bc,分别表示块设备字符设备
  • 设备文件没有文件长度,而增加了另外的两个值,分别是主设备号从设备号。

2、网卡设备

字符设备和块设备不是内核管理的全部设备类别。网卡没有设备文件。用户程序必须使用套接字与网卡通信。套接字是一个抽象层,对所有网卡提供了一个抽象视图。标准库的网络相关函数调用socketcall系统调用与内核通信交互,进而访问网卡。

四、注册

1、设备数据库

每个字符设备都表示为struct cdev的一个实例。

struct kobj_map { struct probe { struct probe *next; dev_t dev;//设备号 unsigned long range;//设备号的范围 struct module *owner;//提供设备驱动程序的模块 kobj_probe_t *get;//指向可以返回与设备关联的kobject实例 int (*lock)(dev_t, void *); void *data;//字符设备指向 struct cdev实例;块设备 指向struct genhd实例 } *probes[255]; struct mutex *lock; }; 

2、字符设备范围数据库

管理为驱动程序分配的设备号范围,确保指定的范围不与现存的范围重叠。

static struct char_device_struct { struct char_device_struct *next; unsigned int major;//主设备号 unsigned int baseminor;//baseminor是包含minorct个从设备号的连续范围中最小的从设备号 int minorct; char name[64];//标识 struct cdev *cdev; /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

3、设备号

1)动态静态设备号申请

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);

void cdev_init(struct cdev *cdev, const struct file_operations *fops); int cdev_add(struct cdev *p, dev_t dev, unsigned count);

在获取了设备号范围之后,需要将设备添加到字符设备数据库,以激活设备。在cdev_add成功返回后,设备进入活动状态。

2)动态创建设备文件

每当内核检测到一个设备时,都会创建一个内核对象kobject 。该对象借助于 sysfs 文件系统导出到用户层 。内核还向用户空间发送一个热插拔消息。

udevd是一个守护进程,允许从用户层动态创建设备文件。

如果在系统启动期间发现新设备,或在运行期间有新设备接入(如USB 存储棒),内核产生的热 插拔消息包含了驱动程序为设备分配的主从设备号。udevd 守护进程所需完成的所有工作,就是监听 这些消息。在注册新设备时,会在/dev 中创建对应的项,接下来就可以从用户层访问该设备了。

五、与文件系统关联

1、 inode中的设备文件成员

虚拟文件系统中的每个文件都关联到一个inode,用于管理文件属性。inode与驱动程序有关的成员如下:

struct inode { ... dev_t i_rdev; //标识与一个设备文件关联的设备 ... umode_t i_mode; ... struct file_operations *i_fop; //一组函数指针的集合 ... union { ... struct block_device *i_bdev; struct cdev *i_cdev; }; ... };

2,标准文件操作

在打开一个设备文件时,各种文件系统的实现会调用init_special_inode函数,为块设备或字 符设备文件创建一个inode。

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_mode = mode; if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = rdev; } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = rdev; } else if (S_ISFIFO(mode)) inode->i_fop = &pipefifo_fops; else if (S_ISSOCK(mode)) ; /* leave it no_open_fops */ else printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for" " inode %s:%lu\n", mode, inode->i_sb->s_id, inode->i_ino); } EXPORT_SYMBOL(init_special_inode);

3,字符设备的标准操作

struct file_operations def_chr_fops = { .open = chrdev_open, };

chrdev_open函数的主要任务就是向该结构填入适用于已打开设备的函数指针, 使得能够在设备文件上执行有意义的操作,并最终能够操作设备自身。

4、字符设备的表示

struct cdev { struct kobject kobj; //一个嵌入在该结构中的内核对象,它用于该数据结构的一般管理 struct module *owner; //指向驱动程序的模块 const struct file_operations *ops; //硬件通信的具体操作 struct list_head list; dev_t dev; //设备号 unsigned int count; };

六、资源管理

为使得各种不同的驱动程序彼此互不干扰,有必要事先为驱动程序分配端口和I/O内存范围。这确保几种设备驱动程序不会试图访问同样的资源。

struct resource { resource_size_t start; //start和end类型为unsigned long,指定了一个一般性的 区域 resource_size_t end; const char *name; unsigned long flags; //用于更准确地描述资源及其当前状态 struct resource *parent, *sibling, *child; //父子兄弟之间关系 }; 

资源分配它连续地扫描现存的资源,将新资源添加到正确的位置,或发现与已经分配区域的冲突。完成所述工作,需要遍历兄弟结点的链表。如果 所需的资源区域是空闲的,则插入新的resource实例,这样就完成了资源的分配。如果该区域不是空 闲的,则分配失败。

//资源请求 static struct resource * request_resource(struct resource *root, struct resource *new); //资源释放 void release_resource(struct resource *old)

七、结构体总结

上述介绍的结构体在内核中的使用如下

内核启动start_kernel到kobj_map注册

(内核免费课程链接:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂


本网页由快兔兔AI采集器生成,目的为演示采集效果,若侵权请及时联系删除。

原文链接:https://blog.csdn.net/WANGYONGZIXUE/article/details/123510752

更多内容