透过GDK8窥探Linux Kernel

前言

理解操作系统有助于定位和排查线上疑难杂症,掌握内核的关键机制,可以加深对程序运行机制的理解,为编写高性能代码和解决性能问题提供强有力的底座支撑。
内核模块是上手Linux内核的一条门径,通过内核模块进而扒开Linux内核高深莫测的面纱。以内核模块为入手点,后续依次整理出内存管理、进程管理的相关内容;

一、背景

1.目标

a. 通过llaolao驱动梳理文件调用流程
b. 通过驱动来窥探文件系统
c. 通过ixgbe驱动注册流程,对网卡驱动有个感性认知

二、调试刘姥姥驱动

1. 实验环境

1. 1 实现环境介绍

使用GDK8调试VFS视频
GDK8相关资料
实验代码

以上调试环境均来自盛格塾

1. 2 触发条件

cat /proc/llaolao

流程分析: cat命令对应的进程,以/proc/llaolao作为全路径,触发系统调用。

下面函数调用栈即内核的整个流程

2. 函数调用栈

在这里插入图片描述

3. 调用逻辑解析

在这里插入图片描述

4. 汇编代码分析

4.1 第0帧proc_lll_read()函数

在这里插入图片描述
在这里插入图片描述

4.2 第1帧proc_reg_read()函数

在这里插入图片描述

4.3 第2帧__vfs_read()函数

在这里插入图片描述

4.4 第3帧vfs_read()函数

在这里插入图片描述
在这里插入图片描述

4.5 第4帧ksys_read()函数

在这里插入图片描述
在这里插入图片描述

4.6 第7帧el0_svc_handler()函数

在这里插入图片描述

5. 内容小结

5.1 ARMv8汇编基础

在这里插入图片描述

5.2 方法论小结

linux kernel代码量庞大,学习和调试难度大,该章节从宏观和微观两个角度来介绍;

a. 宏观角度 -> 业务流程
b. 微观角度 -> 具体实现 + 汇编代码;理解汇编代码逻辑结构,为内核调试奠定基础。

汇编代码总结

a.根据stp和ldp指令,确定函数的范围区间;
b.根据bl等指令找到子函数调用逻辑;
c.调用子函数前,为函数准备参数;

三 初探文件系统

1. 课前思考

1.1 问题域

a. 文件系统要解决哪些问题?
b.为什么抽象出虚拟文件系统?
c. 从冯·诺依曼体系结构的角度来认识文件系统的地位及价值

1.2 个人理解

a. 文件系统负责文件组织、存储、查找及数据加载;在Unix/Linux系统中,一切设备皆文件,设备管理是文件系统的重要组成部分;
b. 虚拟文件系统与设计模式中的抽象工厂模式如出一辙,向用户提供统一的接口,屏蔽底层业务逻辑细节,兼容不同的文件系统;
c. 冯·诺依曼体系架构是以内存为中心的存储程序思想,虽然内存是核心,但内存容量有限且无法永久存储,因此IO很好的解决了这个问题;

2.宏观视图

2.1 逻辑视图

在这里插入图片描述

2.2 读文件过程

a. 进程通过系统调用向内发发起读文件操作;
b. 系统根据系统中断号,在系统调用表中选择相应的系统调用;
c. 通过进程task_struct找到文件描述符,查看该文件是否有缓存,若已缓存则直接读取;
d. 若无缓存,进一步找到dentry及inode信息,并将相应信息缓存内存中,并在AddressSpace记录其相关信息;
e. 若AddressSpace中出现缓存缺页,则产生缺页中断,根据inode信息加载磁盘中相应的数据信息;

3.理解四大对象

在Unix/Linux系统中,将外设统一抽象为文件,文件系统负责文件的组织和管理,VFS屏蔽底层具体的设备/文件系统,向用户提供统一的访问接口。Linux kernel抽象出四大核心对象用于文件系统的组织和管理,SuperBlock和inode用于组织文件系统,file对象记录了进程访问文件的上下文,dentry为文件名和inode建立映射关系,提高系统访问效率;

3.1 超级块

在Linux系统中存在N个文件系统(如ext2),kernel用super_block来组织所有文件系统,并以链表的形式组织起来;
每个文件系统拥有唯一的设备编号、文件系统类型、文件系统操作方法、inode数及inode相关信息;
文件系统初始化时确定了inode的个数;

下面截取了部分代码以供参考

struct super_block { //
	struct list_head		s_list;		//超级块链表
	dev_t			    	s_dev;		//超级块对应的设备编号
	unsigned char			s_blocksize_bits;
	unsigned long			s_blocksize;
	loff_t			    	s_maxbytes;	/* Max file size */
	struct file_system_type	*s_type;  //具体文件系统类型
	const struct super_operations	*s_op; //具体文件类型的操作方法
	struct dentry			*s_root;
	/***/
	
	const struct xattr_handler **s_xattr; 
	spinlock_t				s_inode_list_lock ____cacheline_aligned_in_smp;
	struct list_head		s_inodes;	/* 某一具体文件系统所有的inode节点 */

	spinlock_t				s_inode_wblist_lock;
	struct list_head		s_inodes_wb;	/* 回写节点内容 */
} __randomize_layout;

3.2 inode

文件系统中文件的逻辑概念就是用inode来描述的,且每个文件都拥有唯一的inode编号,即使用ls命令所列举出的文件。
inode节点记录了inode所属用户、用户组、访问及修改时间、所在设备ID、file_operations等相关信息,字段信息可参考所截取的代码;

struct inode { 

	kuid_t			i_uid; //用户ID
	kgid_t			i_gid; //用户组权限

	const struct inode_operations	*i_op; //inode操作对象
	struct super_block		*i_sb; //所属超级块
	struct address_space	*i_mapping; //缓存数据

	unsigned long		i_ino; //每个文件都拥有唯一的ino;

	dev_t				i_rdev;
	loff_t				i_size;
	struct timespec64	i_atime; //文件访问、修改时间
	struct timespec64	i_mtime;
	struct timespec64	i_ctime;
	
	const struct file_operations	*i_fop; //inode对应的文件操作

	void			*i_private; /* fs or device private pointer */
} __randomize_layout;

3.3 file

问题场景:当多个进程同时访问磁盘上的同一个文件时,并且每个进程访问文件的位置不同,如何将访问的上下文记录下来?

super_block和inode解决了文件组织和存储的问题,当多进程访问文件时,可大致分为两部分信息:①文件原始信息,即inode数据;②进程访问文件的上下文;如果每个进程访问文件时,将文件的原始均拷贝一份,那将非常浪费空间;inode和file可认为是两个维度的信息,一个是进程访问文件的动态信息,一个是文件存储的静态信息;但两者会有交互,某个进程会写文件,会修改静态的原始数据;

file数据结构记录了进程访问文件的上下文 【备注:在2.x内核中,file结构体与super_block耦合在一起】

struct file { 

	struct path			f_path; // 文件路径
	struct inode		*f_inode;// 指向文件的原始信息
	const struct file_operations	*f_op; //每个文件对应的操作方法
	
	atomic_long_t		f_count; //文件引用计数
	loff_t				f_pos; // 访问文件的位置
	struct fown_struct	f_owner;
	
	struct address_space	*f_mapping; 

} __randomize_layout __attribute__((aligned(4)));;

3.4 dentry

内存和磁盘的访问时间至少相差一个数量级,检索一个文件有可能会多次访问磁盘,导致大量的Cache miss,严重影响系统性能。通过dentry缓存inode的内容,可减少内存及磁盘访问,提高系统性能;

struct dentry { 

	struct dentry *d_parent;	/* parent directory */
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is
					 * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */
	
	const struct dentry_operations *d_op;
	struct super_block *d_sb;	/* The root of the dentry tree */
	unsigned long d_time;		/* used by d_revalidate */

} __randomize_layout __attribute__((aligned(4)));;

四 理解网卡驱动

1.课前思考

1.1 问题域

a. 如何组织和管理硬件设备及驱动程序?
b. 外部设备是如何接入到操作系统的?相关的系统接口是什么?
c. 计算机系统是如何访问设备存储空间的?设备是如何与计算机系统进行通信?
d. 外部设备与计算机系统交互的内容都有哪些?如何访问硬件设备的物理内存?外设如何与CPU之间进行交互?
e. 驱动模型都有哪些数据结构?他们之间的逻辑关系是什么?
f. 驱动模型中核心的概念都有哪些?
g. 设备发现的逻辑是什么?设备和驱动匹配的逻辑是什么?

1.2 个人理解

内核管理外设,提供了接入系统的接口。计算机系统需要和外设进行交互,访问外设物理存储空间;外设以异步的方式进行工作,以中断的方式与CPU进行交互。每个外设均需要配置物理内存空间、响应中断、驱动与设备匹配标识,需要将这些配置信息与业务逻辑解耦合,在计算机发展过程中提出了PnP的解决方案,在ARM中以DTS的形式来实现灵活配置硬件的所需的相关数据。

设备驱动主要包括以下几个方面逻辑:①框架接入代码,包括驱动模板(ixgbe_driver)及操作方法(ixgbe_netdev_ops);②业务逻辑,涉及到具体设备的相关业务细节,如ixgbe,有数据链路层、物理层、存储信息

2.网卡注册流程

2.1 宏观视图

在这里插入图片描述

2.2 注册流程

2.2.1 触发驱动注册

内核模块是接入kernel的一种方式,insmod触发了驱动注册流程,module_init()为接入kernel的入口函数

2.2.2 总线中注册驱动

pci_register_driver()为将驱动程序接入到驱动管理框架的接口,在执行过程中触发用户业务逻辑(ixgbe_probe),创建/sys/devxxx目录

2.2.3 触发驱动挂载逻辑

驱动挂载逻辑主要包括:为设备配置物理地址空间、设置中断、创建设备并将设备注册到系统中;

2.2.4 将ixgbe_poll方法注册到napi_hash结构中

kernel使用vector + hashtable的设计方式,很好的兼顾了性能和扩展性,hashtable为设备动态注册提供了便利

2.2.4 DTS

DTS中包含中断、物理内存地址空间、设备编号等信息。操作系统在加载时,解析DTS相关配置文件,在设备驱动注册时,根据设备编号来配置中断号、物理内存等相关信息

2.3 网卡收包流程

a. 网卡收到网络包;
b. 网卡把数据DMA到内存中;
c. 网卡向CPU发出硬中断,CPU相应硬中断,简单处理后触发软中断;
d. ksoftirq内核线程根据中断向量表,找到注册到napi_hash中的ixgbe_poll方法,开始收包;
e. 数据帧被从RingBuffer中摘下,生成skb进入协议栈,进入协议栈处理逻辑;

五 闲侃学习内核的思路

5.1 学习的难点

a. 内核代码量庞大,数据结构关联度高,函数调用路径长;
b. 涉及知识面广,涉及硬件、编译器、汇编等方面;

5.2 个人理解

a. 拥有宏观和微观视角,即拥有大局观和小处着手的能力。从业务需求和技术发展的宏观角度来入手,建立大局观,从上到下的视角来梳理代码。

问题场景是什么 → 为解决问题抽象出哪些概念 → 抽象出哪些数据结构 → 深入某个技术细节。以内存管理为例:为什么要有内存(由图灵机和冯·诺依曼体系架构决定)? → 内存发展中遇到了哪些问题(内存贵而且不够用,是稀有资源) → 虚拟内存技术 → 物理内存和虚拟内存如何组织,两者如何交互的?→ 以用户进程视角,其物理内存和虚拟内存是如何组织的?→ 深入某个技术细节

b. 操作系统为硬件的大管家,站在管理的角度来理解内核。如设备管理,内核提供了设备管理的框架,这部分提供了组织和管理的作用,框架一般包括:接入模块、触发用户逻辑模块;
c. 从多角度思考内核;内核内容繁杂,可尝试从计算机工作过程、应用程序使用资源、内核提供高效组织资源使用资源及提供优质服务的角度;
d. 拥有穿针引线的能力,用问题及宏观视图作为骨架将数据结构和关键流程串联起来;
e. 内核代码只是冰山一角,要挖掘代码背后的问题场景,其解决问题的思路以及代码设计的高明处;
f. 不结合实际的哲学是空洞的,不结合细节的方法是绣花枕头。方法只是为了看清事情的脉络,有方法有细节才能更好掌握内核。

六 后记

以上为内核学习的思路及内容整理,主要以图示的方式来展示并未涉及太多细节

学习内核要拥有大局观等很多思想均来自于张银奎老师,张老师是学习内核路上的良师,在这里感谢张老师

参考资料:

《在调试器下理解计算机系统》
《在调试器下理解Armv8》
《深入理解Linux网络》


  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值