1.引入
Linux内核的整体架构本就非常庞大,其包含的组件也非常多。而我们怎样把需要的部分都包含在内核中呢?
一种方法是把所有需要的功能都编译到Linux内核中。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核。有没有另一种机制可使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中呢?
Linux 提供了这样的机制,这种机制被称为模块(Module)。模块具有这样的特点,模块本身不被编译人内核映像,从而控制了内核的大小。模块一旦被加载,它就和内核中的其他部分完全一样。
Linux驱动开发就是编写上面提到的模块。
应用程序和 VFS 之间的接口是系统调用,而 VFS 与文件系统以及设备文件之间的接口是 file_operations
结构体成员函数,这个结构体包含对文件进行打开、关闭、读写、控制的系列成员函数,关系如图5.2所示。

由于字符设备的上层没有类似于磁盘的ext2等文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了file_operations正是字符设备驱动的核心。
块设备有两种访问方法:
一种方法是不通过文件系统直接访问裸设备,在 Linux内核实现了统一的 def blk_fops
这一file_operations
,它的源代码位于fs/block_dev.c,所以当我们运行类似于"dd if=/dev/sdbl of-sdbl.img"
的命令把整个 /dev/sdb1 裸分区复制到 sdbl.img的时候,内核走的是def blk_fops
这个file_operations
;
另外一种方法是通过文件系统来访问块设备,file_operations
的实现则位于文件系统内,文件系统会把针对文件的读写转换为针对块设备原始扇区的读写。ext2、fat、Btrfs等文件系统中会实现针对VFS的file operations成员函数,设备驱动层将看不到file_operations
的存在。
!
2.struct file、struct inode
在设备驱动程序的设计中,一般面言,会关心file
和inode
这两个结构体
file结构体代表一个打开的文件,系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的在内核和驱动源代码中,struct file的指针通常所有实例都关闭后,内核释放这个数据结构。被命名为file或filp(即 file pointer)。
C
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 */
文件读/写模式mode、标志flags 都是设备驱动关心的内容,而私有数据指针 private_data在设备驱动中被广泛应用,大多被指向设备驱动自定义以用于描述设备的结构体。
struct inode
包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。
C
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 rw_semaphore i_rwsem;
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;
struct list_head i_wb_list; /* backing dev writeback 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;
unsigned i_dir_seq;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
struct fscrypt_info *i_crypt_info;
#endif
void *i_private; /* fs or device private pointer */
};
对于表示设备文件的inode结构,struct cdev *i_cdev;
字段包含设备编号。Linux内核设备编号分为主设备编号和次设备编号,前者为devt的高12位.后者为 devt的低 20位。
3.udev
**devfs(设备文件系统)**是由Linux2.4内核引入的,引入时被许多工程师给予了高度评价它的出现使得设备驱动程序能自主地管理自己的设备文件。具体来说,devs具有如下优点
- 可以通过程序在设备初始化时在/dev目录下创建设备文件,卸载设备时将它删除
- 设备驱动程序可以指定设备名、所有者和权限位,用户空间程序仍可以修改所有者和权限位。
- 不再需要为设备驱动程序分配主设备号以及处理次设备号,在程序中可以直接给register chrdev()传递0主设备号以获得可用的主设备号,并在devfsregister()中指定次设备号。
尽管 devfs有这样和那样的优点,但是,在Linux2.6内核中,devfs被认为是过时的方法,并最终被抛弃了,udev取代了它。
在嵌人式系统中,也可以用 udev 的轻量级版本 mdev,mdev 集成于 busybox 中。
Linux设计中强调的一个基本观点是机制和策略的分离。机制是做某样事情的固定步骤、方法,而策略就是每一个步骤所采取的不同方式。机制是相对固定的,而每个步骤采用的策略是不固定的。机制是稳定的,而策略则是灵活的,因此,在Linux内核中,不应该实现策略。
热插拔:
udev完全在用户态 工作利用设备加人或移除时内核所发送的热插拔事件(Hotplug) ,在热插拔时,设备的详细信息会由内核通过netlink
套接字发送出来,发出(Event
)来工作的事情叫 uevent
。udev的设备命名策略、权限控制和事件处理都是在用户态下完成的,它利用从内核收到的信息来进行创建设备文件节点等工作。
冷插拔:
udev就是采用这种方式接收netlink消息,并根据它的内容和用户设置给udev的规则做匹配来进行工作的。这里有一个问题,就是冷插拔的设备怎么办?冷插拔的设备在开机时就存在,在udev启动前已经被插入了。对于冷插拔的设备,Linux内核提供了sysfs下面一个uevent节点,可以往该节点写一个"add",导致内核重新发送netlink,之后udev就可以收到冷插拔的netlink 消息了。
shell
udev自动加载驱动模块的原理
#设备被发现时发送事件:
当硬件设备被插入系统时,内核会检测到该设备,并通过kobject_uevent函数向用户空间发送一个uevent事件。
这个事件包含了设备的相关信息,如设备的Vendor ID、Product ID等。
#udev监听事件并处理:
udev守护进程在用户空间运行,监听这些uevent事件。
udev根据事件中的设备信息,查找/lib/modules/uname-r/modules.alias文件,确定需要加载的驱动模块。
#加载驱动模块:
udev通过调用modprobe命令加载对应的驱动模块。
modprobe会根据模块的依赖关系,自动加载所有必要的依赖模块。
#注意:
/lib/modules/uname-r/modules.alias文件的内容是在内核模块编译和安装时由make modules_install命令生成的。它包含了模块的别名映射,用于帮助udev或其他工具根据设备的硬件信息找到并加载正确的驱动模块。模块编译进内核时不会出现在modules.alias文件中,只有作为可加载模块编译时才会被包含在内。insmod命令用于手动加载模块,但不会修改modules.alias文件。
**udev的设计者认为inux应该在设备被发现的时候加载驱动模块,而不是当它被访问的时候。**udev的设计者认为devs所提供的打开/dev节点时自动加载驱动的功能对一个配置正确的计算机来说是多余的,系统中所有的设备都应该产生热插拔事件并加载恰当的驱动,而udev能注意到这点并且为它创建对应的设备节点。
4.sysfs文件系统与Linux设备模型
Linux 2.6以后的内核引人了sysfs 文件系统 ,sys被看成是与 proc、devfs和 devpty 同类别的文件系统,该文件系统是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,与提供进程和状态信息的proc文件系统十分类似。
sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。sysfs的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括 block、bus、dev、devices、class、fs、kernel、power和 frmware 等。
block目录包含所有的块设备;devices目录包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构;bus日录包含系统中所有的总线类型;class目录包含系统中的设备类型(如网卡设备、声卡设备、输人设备等)。

在 /sys/bus的 pci等子目录下,又会再分出 drivers和devices目录,而devices 目录中的文件是对/sys/devices目录中文件的符号链接。同样地,/sys/class目录下也包含许多对/sys/devices下文件的链接。如图5.3所示,Linux设备模型与设备、驱动、总线和类的现实状况是直接对应的,也正符合Linux2.6以后内核的设备模型。

大多数情况下,Linux2.6以后的内核中的设备驱动核心层代码作为"幕后大佬"可处理好这些关系,内核中的总线和其他内核子系统会完成与设备模型的交互 ,这使得驱动工程师在编写底层驱动的时候几乎不需要关心设备模型,只需要按照每个框架的要求,"填鸭式地填充xxx driver里面的各种回调函数 ,xxx是总线的名字,在 Linux 内核中,分别使用 bus type
、device driver
和 device
来描述总线、驱动和设备,这3个结构体定义于include/linux/device.h
头文件中,其定义如代码清单5.7所示。
c
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
C
struct device_driver {
const char *name; // 驱动程序的名称
struct bus_type *bus; // 驱动程序所属的总线类型(如 platform_bus_type、i2c_bus_type 等)
// 内核通过总线类型将驱动程序与设备匹配
struct module *owner; // 指向拥有该驱动程序的模块(通常使用 THIS_MODULE)
const char *mod_name; // 用于内置模块的名称(通常用于模块卸载时)
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
// 如果为 true,则禁用通过 sysfs 进行驱动程序的绑定和解绑操作
enum probe_type probe_type; // 设备探测类型,用于控制探测行为(如同步探测或异步探测)
const struct of_device_id *of_match_table; // 设备树匹配表,用于支持设备树(Device Tree)的设备匹配
const struct acpi_device_id *acpi_match_table; // ACPI 匹配表,用于支持 ACPI 设备的匹配
int (*probe) (struct device *dev); // 设备探测函数,当驱动程序与设备匹配成功时调用
int (*remove) (struct device *dev); // 设备移除函数,当设备从系统中移除时调用
void (*shutdown) (struct device *dev); // 设备关闭函数,当系统关闭时调用
int (*suspend) (struct device *dev, pm_message_t state);// 设备挂起函数,当设备进入休眠状态时调用
int (*resume) (struct device *dev); // 设备恢复函数,当设备从休眠状态恢复时调用
const struct attribute_group **groups; // 驱动程序的属性组
const struct dev_pm_ops *pm;// 电源管理操作集,用于定义设备的电源管理操作
struct driver_private *p; // 驱动程序私有数据
};
C
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated thisdevice */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
};
注点:总线、驱动和设备最终都会落实为sysfs中的1个目录 ,因为进一步追踪代码会发现,它们实际上都可以认为是 kobject
的派生类,kobject
可看作是所有总线、设备和驱动的抽象基类,1个kobject
对应sys中的1个目录.
总线设备和驱动中的各个attribute 直接落实为sysfs中的一个文件 ,attribute 会伴随着show()
和store()
这两个函数。分别用于读写该attribute 对应的sysfs文件。
下面给出了 attribute、bus attribute、driver attribute 和 device attribute 这几个结构体的定义。
c
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf,
size_t count);
};
事实上sysfs中的目录来源于 bus type、device_driver、device,而目录中的文件则来源attribute.
Linux内核中也定义了一些快捷方式以方便attribute的创建工作:
C
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \
struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \
struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DRIVER_ATTR_RW(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
#define DEVICE_ULONG_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }
#define DEVICE_INT_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }
#define DEVICE_BOOL_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }
#define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = \
__ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)
比如,我们在 drivers/base/bus.c文件中可以找到这样的代码
C
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);
static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
show_drivers_autoprobe, store_drivers_autoprobe);
而在 /sys/bus/platform 等里面就可以找到对应的文件
shell
[root@100ask:/sys/bus/platform]# ls
devices drivers drivers_autoprobe drivers_probe uevent