8. Linux 内核驱动详情 USB 转串口驱动开发1. uboot 引导 Linux 内核启动2. Linux 内核启动2.1 Linux 内核启动---汇编代码初始化 C 运行环境2.2 start_kernel2.2.1 start_kernel--setup_arch解析设备树和内核参数2.2.2 Linux 内核启动---start_kernel初始化 VFS创建临时根目录 /2.2.3 Linux 内核启动---start_kernel-- kernel_init--do_basic_setup驱动框架2.2.4 Linux 内核启动---start_kernel--kernel_init--临时根目录 / 切换到真实根目录执行用户态进程 init1. 注册 usb-serial 虚拟总线 bus_register(bus_type)1.1 总线的抽象 bus_typeusb_serial_bus_type2. 注册 TTY 驱动 tty_register_driver(tty_driver)2.1 TTY 的抽象 tty_driverusb_serial_tty_driver3. 注册 USB 设备驱动[ usbserial.ko ] usb_register(usb_driver)3.1 USB 设备驱动的抽象 usb_driverusb_serial_tty_driver1. uboot 引导 Linux 内核启动2. Linux 内核启动2.1 Linux 内核启动—汇编代码初始化 C 运行环境2.2start_kernelstart_kernel ├─vfs_caches_init_early()//为全局变量 dentry_hashtable 和 inode_hashtable 分配一块连续物理内存并初始化这块内存中的每个桶使其成为一个空的链表头为后续的 dentry 和 inode 插入和查找做好准备└─vfs_caches_init(totalram_pages)//初始化 VFS虚拟文件系统层的核心,注册两个关键的虚拟文件系统sysfs 和 rootfs|├─dcache_init()//1. 初始化目录项dentry缓存此后VFS 可以通过 dentry 缓存快速将路径名解析为目录项而无需每次都从磁盘读取从而大幅提升文件路径查找性能|├─inode_init()//2. 初始化索引节点inode缓存此后VFS 可以通过 inode 缓存快速根据超级块、ino 号定位到内存中的 inode避免频繁从磁盘读取同时管理文件元数据权限、大小、时间戳等|├─files_init(mempages)//3. 初始化文件对象file 缓存此后VFS 可通过 filp_cachep 高效分配文件对象并通过 files_stat.max_files 限制并发打开文件的数量|├─mnt_init()//4. 初始化挂载点mount相关数据结构为后续文件系统挂载做准备||├─ 一系列初始化//4.1 初始化挂载对象struct mount缓存||├─kernfs_init()//4.2 初始化 kernfs 内部数据结构kernfs 是 sysfs、cgroup 等伪文件系统的底层框架||├─sysfs_init()//4.3 注册并初始化 sysfs 文件系统通常在 “用户空间 init” 阶段完成虚拟文件系统挂载|||├─kernfs_create_root()// 创建 sysfs 的根 kernfs 节点|||└─register_filesystem()// 向内核注册 sysfs_fs_type一个 struct file_system_type使系统能够识别并挂载 sysfs||├─kobject_create_and_add(fs,NULL)//4.4 在 sysfs 根目录下创建 fs 目录即 /sys/fs/用于存放文件系统相关的属性|||├─kobject_create()// 创建并初始化 kobject 实例|||└─kobject_add()// 将 kobject 实例添加到 sysfs 树即在 /sys 下创建 fs 目录用于后续挂载文件系统相关的属性文件如 /sys/fs/cgroup、/sys/fs/ext4 等||├─init_rootfs()//4.5 注册并初始化根文件系统通常是 rootfs一种基于 tmpfs 的内存文件系统为后续挂载真正的根文件系统做准备|||├─register_filesystem()// 注册 rootfs 文件系统类型将 rootfs_fs_type 注册到内核文件系统列表。rootfs 是一个特殊的伪文件系统通常作为初始根文件系统使用|||└─shmem_init()// 初始化 tmpfs共享内存文件系统||└─init_mount_tree()//4.6 挂载根文件系统、创建并初始挂载命名空间、并将其绑定到 init 进程同时设置了 init 进程的工作目录和根目录后续通过 prepare_namespace() 切换到真正的磁盘根文件系统||├─get_fs_type(rootfs)// 获取 rootfs 文件系统类型即通过名称 rootfs 在内核已注册的文件系统列表中查找对应的 file_system_type||├─vfs_kern_mount(type,0,rootfs,NULL)// 创建一个新的 vfsmount 实例并为其分配一个根 dentry并将 rootfs 挂载到虚拟的挂载点上这个挂载点就是 VFS 的根目录 /即根 dentry||├─ nscreate_mnt_ns(mnt)// 分配并初始化一个新的 mnt_namespace 结构将刚才挂载得到的 vfsmount 作为该命名空间的根挂载点||├─ init_task.nsproxy-mnt_nsns// 将新建的命名空间赋值给 initPID 1 的进程描述符的命名空间代理nsproxy中的 mnt_ns 字段||├─set_fs_pwd()// 设置当前进程此时为 init 进程的当前工作目录为根目录 /||└─set_fs_root()// 设置当前进程的根目录 / 也为该挂载点使进程的路径解析从根文件系统开始即创建根目录 /|├─bdev_cache_init()//5. 初始化块设备block device的缓存此后内核可以通过 bdev_cachep 高效分配/回收块设备 inode并将这些 inode 组织在该伪文件系统的目录树中实现块设备与 VFS 的统一管理|└─chrdev_init()//6. 初始化字符设备驱动注册表内核拥有了一个全局的字符设备映射表 cdev_map。此后驱动程序可以通过 cdev_add 将字符设备插入该映射表用户空间通过 open 系统调用访问设备时VFS 会根据设备号在此映射表中找到对应的 cdev进而调用其 file_operations。这是字符设备框架的核心初始化步骤|└─rest_init()└─kernel_thread(kernel_init,NULL,CLONE_FS)// 创建 PID 1init 进程└─kernel_init()├─kernel_init_freeable()|├─do_basic_setup()// 执行基本的设备驱动初始化|├─prepare_namespace()// 挂载真正的根文件系统||├─mount_block_root()//1. 用户通过 root 指定了根设备如 root/dev/sda1 或 rootUUIDxxx对于闪存设备MTD、UBI直接挂载||├─initrd_load()//2. 通过内核参数 initrd 或在引导加载器中加载的 RAM disk将 initrd 内容解压并作为临时根文件系统挂载然后执行其中的 /init 程序切换到真正的根文件系统||├─mount_root()//3. 挂载真正的根文件系统|||├─mount_nfs_root()//3.1 内核参数 root/dev/nfs 或 rootnfs解析 NFS 服务器地址、导出路径等参数并执行挂载|||├─create_dev(/dev/root,ROOT_DEV)//3.2 在内核的设备文件系统如 devtmpfs 或 sysfs中创建一个名为 /dev/root 的设备节点其设备号等于 ROOT_DEV。这是一个临时的设备节点用于后续挂载|||└─mount_block_root(/dev/root,***)//3.3 根据内核参数 rootfstype 指定的文件系统类型或者遍历内核支持的所有文件系统类型如 ext4、btrfs、xfs 等逐一尝试挂载直到成功为止||├─devtmpfs_mount(dev)//4. 在新挂载的根文件系统上创建 /dev 目录并挂载 devtmpfs使得设备节点自动生成||├─sys_mount(.,/,NULL,MS_MOVE,NULL)//5. 当前目录新根移动到 / 挂载点实质上是将临时根如 initramfs 的根替换为真正的根文件系统同时卸载旧的根||└─ssys_chroot(.)//6. 将当前进程的根目录切换到新的根目录即真正的根文件系统此后所有路径引用都基于此|||└─load_default_modules()// 加载默认内核模块根据内核配置和硬件信息自动加载必要的模块如文件系统驱动、块设备驱动等确保根文件系统能够正常访问|└─try_to_run_init_process()// 按顺序尝试执行默认的 init 程序/sbin/init → /etc/init → /bin/init → /bin/shrest_init函数2.2.1start_kernel–setup_arch解析设备树和内核参数尝试从设备树物理地址中匹配机器描述符2.2.2 Linux 内核启动—start_kernel初始化 VFS创建临时根目录 /通过 vfs_caches_init_early 函数完成一系列的初始化工作通过 vfs_caches_init 函数完成一系列的初始化工作2.2.3 Linux 内核启动—start_kernel–kernel_init–do_basic_setup驱动框架通过devtmpfs_init函数完成下列任务将devtmpfs文件系统插入到全局链表file_systems中将devtmpfs文件系统覆盖挂载到rootfs的临时根目录 /后续会通过用户空间命令mount将其挂载到/dev循环等待和处理设备节点请求队列requests当有请求时遍历整个链表逐个处理在devtmpfs挂载点下创建或删除设备节点通过devices_init函数完成下列任务创建/sys/devices目录即devices_kset创建/sys/dev目录即dev_kobj创建/sys/dev/block目录即sysfs_dev_block_kobj创建/sys/dev/char目录即sysfs_dev_char_kobj通过 buses_init 函数完成下列任务创建/sys/bus目录即bus_kset创建/sys/devices/system目录即system_kset通过 classes_init 函数完成下列任务创建/sys/class目录即class_kset通过 firmware_init 函数完成下列任务创建/sys/firmware目录即firmware_kobj通过 hypervisor_init 函数什么都不做通过platform_bus_init函数完成下列任务初始化platform_bus结构体struct device初始化platform_bus_type结构体struct bus_type创建/sys/devices/platform/uevent属性文件创建/sys/bus/platform/uevent属性文件创建/sys/bus/platform/drivers_probe属性文件创建/sys/bus/platform/drivers_autoprobe属性文件创建/sys/devices/platform目录创建/sys/bus/platform目录创建/sys/bus/platform/devices目录创建/sys/bus/platform/drivers目录通过 cpu_dev_init 函数完成下列任务初始化cpu_subsys结构体创建/sys/bus/cpu目录并将其插入到链表中创建/sys/bus/cpu/devices目录创建/sys/bus/cpu/drivers目录创建/sys/bus/cpu/drivers_autoprobe属性文件创建/sys/bus/cpu/drivers_probe属性文件创建/sys/bus/cpu/uevent属性文件初始化cpu_subsys-dev_root 结构体创建/sys/devices/system/cpu目录并将cpu_subsys-dev_root插入到system_kset-kobj.entry链表中创建/sys/devices/system/cpu/uevent属性文件创建/sys/devices/system/cpu/online属性文件创建/sys/devices/system/cpu/offline属性文件创建/sys/devices/system/cpu/cpu0目录及相关属性文件通过 memory_dev_init 函数完成下列任务初始化memory_subsys结构体创建/sys/bus/memory目录并将其插入到链表中创建/sys/bus/memory/devices目录创建/sys/bus/memory/drivers目录创建/sys/bus/memory/drivers_autoprobe属性文件创建/sys/bus/memory/drivers_probe属性文件创建/sys/bus/memory/uevent属性文件初始化memory_subsys-dev_root 结构体创建/sys/devices/system/memory目录并将memory_subsys-dev_root插入到system_kset-kobj.entry链表中创建/sys/devices/system/memory/uevent属性文件创建/sys/devices/system/memory/online属性文件创建/sys/devices/system/memory/offline属性文件通过 device_register 函数为每个内存块创建设备通过 container_dev_init 函数完成下列任务初始化container_subsys结构体创建/sys/bus/container目录并将其插入到链表中创建/sys/bus/container/devices目录创建/sys/bus/container/drivers目录创建/sys/bus/container/drivers_autoprobe属性文件创建/sys/bus/container/drivers_probe属性文件创建/sys/bus/container/uevent属性文件初始化container_subsys-dev_root 结构体创建/sys/devices/system/container目录并将container_subsys-dev_root插入到system_kset-kobj.entry链表中创建/sys/devices/system/container/uevent属性文件创建/sys/devices/system/container/online属性文件创建/sys/devices/system/container/offline属性文件通过 of_core_init 函数完成下列任务创建/sys/firmware/devicetree目录创建/sys/firmware/devicetree/base目录遍历设备树链表中每个设备节点将其创建为/sys/firmware/devicetree/base/xxx目录遍历设备树链表中每个设备节点的所有属性将其创建为相应节点目录下的/sys/firmware/devicetree/base/xxx属性文件2.2.4 Linux 内核启动—start_kernel–kernel_init–临时根目录 / 切换到真实根目录执行用户态进程 init设置 临时根文件系统1.1向内核注册 sysfs 和 rootfs 等虚拟文件系统1.2通过set_fs_pwd()函数将当前进程init_task即 PID 0 的空闲进程的工作目录设置为rootfs的根目录即 /1.3通过set_fs_root)函数将当前进程init_task即 PID 0 的空闲进程的根目录设置为rootfs的根dentry挂载真正的根文件系统2.1通过prepare_namespace()-mount_root() 函数在当前rootfs文件系统下创建/dev/root设备节点遍历全局文件系统链表file_systems中所有已注册的文件系统逐个尝试挂载将新创建的/dev/root设备节点挂载到当前rootfs的/root目录临时目录2.2通过prepare_namespace()-devtmpfs_mount() 函数将字符串devtmpfs以文件系统类型devtmpfs挂载到当前rootfs下的/root/dev目录执行该函数时当前工作目录已经通过 mount_root 函数 切换到了/root2.3通过prepare_namespace()- sys_mount()函数将当前目录.即真实根文件系统挂载的临时挂载点移动到/覆盖原来的rootfs临时根真实根文件系统就成为进程的根目录2.4通过prepare_namespace()-sys_chroot() 函数将当前进程的根目录设置为当前目录即刚移动过来的真实根完成最终的根切换/就指向真实的根文件系统尝试启动用户空间的第一个进程 init3.1ramdisk_execute_command 来自内核引导参数 rdinit…指定 initramfs 中要执行的 init 程序默认为 /init3.2execute_command 来自内核引导参数 init…用于指定真正的根文件系统中的 init 程序例如 init/sbin/myinit3.3按顺序尝试执行默认的 init 程序路径 /sbin/init → /etc/init → /bin/init → /bin/sh真正的根文件系统中的/sbin/init他的主要工作完成用户空间初始化1.1挂载 /proc、/sys、/dev、/run、/tmp 等虚拟文件系统1.2设置主机名、网络接口、时区、locale1.3启动 udev/mdev 管理设备节点和加载固件1.4加载 /etc/modules 中指定的内核模块启动并管理系统服务2.1按依赖关系顺序启动守护进程sshd、cron、syslog、NetworkManager、docker 等2.2监控服务状态自动重启崩溃的服务systemd 的 Restartalways2.3理服务间的依赖如网络服务先于 Web 服务器进程管理与回收3.1收养孤儿进程父进程先退出的子进程getppid() 返回 13.2调用 wait() 回收僵尸进程防止系统资源泄漏3.3响应 SIGCHLD 信号清理终止的子进程处理系统关机/重启4.1捕获 CtrlAltDel 触发重启可配置4.2执行 shutdown、reboot、halt、poweroff 时按顺序终止所有进程、卸载文件系统、调用内核关机接口提供登录会话5.1在虚拟控制台tty1~tty6启动 getty/agetty提供文本登录5.2启动显示管理器gdm、lightdm、sddm提供图形登录1. 注册 usb-serial 虚拟总线 bus_register(bus_type)申请subsys_private结构体大小的内存初始化subsys_private结构体通过kset_register()创建/sys/bus/usb-serial/目录目录属性由subsys_private结构体决定通过bus_create_file()创建/sys/bus/usb-serial/uevent文件用于让用户空间触发总线相关的热插拔事件通过kset_create_and_add()创建/sys/bus/usb-serial/devices/目录用于存放该总线上所有设备的符号链接通过kset_create_and_add()创建/sys/bus/usb-serial/drivers/目录用于存放该总线上所有已注册驱动的符号链接通过add_probe_files()创建/sys/bus/usb-serial/drivers_autoprobe和/sys/bus/usb-serial/drivers_probe文件为用户空间提供手动触发驱动探测或控制自动探测行为的接口通过bus_add_groups()将总线结构体中预定义的属性组批量注册到 sysfs 中1.1 总线的抽象 bus_typeusb_serial_bus_typestructbus_type{constchar*name;/* 总线名对应 /sys/bus/name */constchar*dev_name;/* 自动命名设备用如 foou id */structdevice*dev_root;/* sysfs 里该总线的根设备 */structdevice_attribute*dev_attrs;/* use dev_groups instead */conststructattribute_group**bus_groups;/* 总线自身属性出现在 /sys/bus/name/ */conststructattribute_group**dev_groups;/* 该总线上所有设备的属性出现在 /sys/bus/name/devices/... */conststructattribute_group**drv_groups;/* 该总线上所有驱动的属性出现在 /sys/bus/name/drivers/drv/ */int(*match)(structdevice*dev,structdevice_driver*drv);int(*uevent)(structdevice*dev,structkobj_uevent_env*env);int(*probe)(structdevice*dev);int(*remove)(structdevice*dev);void(*shutdown)(structdevice*dev);int(*online)(structdevice*dev);int(*offline)(structdevice*dev);int(*suspend)(structdevice*dev,pm_message_tstate);int(*resume)(structdevice*dev);conststructdev_pm_ops*pm;conststructiommu_ops*iommu_ops;structsubsys_private*p;/* 驱动核心私有别直接碰 */structlock_class_keylock_key;};2. 注册TTY驱动 tty_register_driver(tty_driver)通过register_chrdev_region()静态注册一段连续字符设备号通过list_add()将当前TTY驱动driver-tty_drivers插入到内核全局的TTY驱动链表tty_drivers通过proc_tty_register_driver()将TTY驱动信息注册到/proc/tty/drivers中用户可以通过读取其中所有已注册的TTY 驱动及其设备号访问2.1TTY的抽象 tty_driverusb_serial_tty_driver3. 注册 USB 设备驱动[ usbserial.ko ] usb_register(usb_driver)申请 usb_driver结构体 大小的内存并初始化结构体部分成员初始化subsys_private结构体通过kset_register()创建/sys/bus/usb-serial/目录目录属性由subsys_private结构体决定3.1 USB 设备驱动的抽象 usb_driverusb_serial_tty_driver