1.Linux内核源码解析---mount挂载原理
2.一文搞懂Linux内核网络设备驱动(白嫖小知识~)
3.Linux 系统内核概述
4.深入剖析Linux文件系统之文件系统挂载(一)(超详细~)
5.字符设备中的几个函数分析
Linux内核源码解析---mount挂载原理
Linux磁盘挂载命令"mount -t xxx /dev/sdb1 abc/def/"的底层实现原理非常值得深入了解。从内核初始化的vfsmount开始说起。
内核初始化过程中,主要关注"main.c"中的vfs_caches_init函数,这个方法与mount紧密相连。接着,php部署源码跟进"mnt_init"和"namespace.c",关键在于最后的三个函数,它们控制了挂载过程的实现。
在"mount.c"中,sysfs_fs_type结构中包含了获取超级块的函数指针,而"init_rootfs"则注册了rootfs类型的文件系统。挂载系统调用sys_mount中的dev_name, dir_name和type参数,分别对应设备名称、挂载目录和文件系统类型。
"do_mount"方法通过path_lookup收集挂载目录信息,创建nameidata结构,然后调用do_add_mount进行实际挂载。这个过程涉及do_kern_mount和graft_tree,尽管具体实现较为复杂,但核心在于创建vfsmount并将其与namespace关联。
在"graft_tree"中的判断逻辑中,vfsmount被创建并与其父mount和挂载目录的dentry建立关系。在"attach_mnt"方法中,新vfsmount与现有结构关联,设置挂载点和父vfsmount,最终形成挂载的概念,即为设备分配vfsmount,并将其与指定目录和vfsmount结合,淘宝红包源码成为vfs系统的一部分。
一文搞懂Linux内核网络设备驱动(白嫖小知识~)
介绍数据包收包过程,有助于我们了解Linux内核网络设备在数据收包过程中的位置。数据包从被网卡接收到进入socket接收队列的整个过程,首先涉及网络设备初始化。以Intel I网卡的驱动ibg为例,驱动会在加载时调用初始化函数。pci_register_driver函数用于将驱动的各种回调方法注册到一个struct pci_driver变量中。通过PCI ID识别设备后,内核为设备选择合适的驱动。许多驱动需要大量代码使得设备就绪,如设置net_device_ops变量,注册ethtool函数,以及配置软中断。
网络设备启动过程中,igb_probe函数完成设备初始化工作,包括PCI相关的操作和通用网络功能。结构net_device_ops变量包含了网络设备相关的操作函数,例如开启网络设备(ndo_open)时会调用对应的方法。在使用DMA将数据直接写入内存后,实现这一功能的数据结构为ring buffer。预留内存区域给网卡使用,实现数据包的接收。网卡支持接收侧扩展(RSS)或多队列技术,以利用多个CPU并行处理数据包。Intel I网卡支持多队列,其驱动在启用时调用igb_setup_all_rx_resources函数管理DMA内存。
启用NAPI(New API)接收数据包,扒会员源码通过调用napi_enable函数设置NAPI变量中的启用标志位。对于igb驱动,当网卡被启用或通过ethtool修改队列数量或大小时,会启用每个q_vector的NAPI变量。注册中断处理函数,不同驱动实现因硬件而异,一般优先考虑MSI-X中断方式,以实现更高效的数据处理。最后,打开硬中断,网卡便可以接收数据包。
监控网络设备有多种方式,从最粗粒度的ethtool -S查看网卡统计信息,到sysfs中获取接收端数据包的详细类型统计。sysfs提供统计信息,但驱动决定何时以及如何更新这些计数。/proc/net/dev提供了更高层级的网卡统计,适合作为常规参考。如果对数据的准确度有高要求,必须查看内核源码、驱动源码和驱动手册,以完全理解每个字段的实际含义。
本文仅介绍了Linux内核网络设备驱动的基本概述,未来将深入探讨更详细的内容,欢迎关注后续文章。
Linux 系统内核概述
Linux内核是一种开源的类Unix操作系统宏内核。
它是Linux操作系统的核心组件,同时也是tez源码分析计算机硬件与进程之间的桥梁。内核负责处理两者之间的通信,并高效地管理资源。内核被称为内核,是因为它在操作系统中扮演着类似种子在果实硬壳中的角色,掌控着硬件的主要功能。内核的主要用途包括以下四项工作:
在正确实施的情况下,内核对用户来说是不可见的,它在自己的小世界中(称为内核空间)工作,分配内存并跟踪内容的存储位置。用户所看到的内容被称为用户空间。这些应用通过系统调用接口(SCI)与内核进行交互。
1. 内核简介
Linux内核采用单内核体系设计,同时借鉴了微内核设计体系的优点,引入了模块化机制。
2. 内核模块
2.1 uname命令
使用格式:uname [选项]
参数解释:[选项]用于指定命令的功能,如-n显示内核名称。
2.2 lsmod命令
显示由核心已经装载的内核模块。
命令定义:lsmod [-v] [-c] [-s] [-m]
字段含义:[-v]显示详细模式,[-c]显示模块数量,[-s]显示模块大小,[-m]显示模块名称。
2.3 modinfo命令
显示模块的详细描述信息。
命令定义:modinfo [模块名称]
语法:modinfo [-v] [模块名称]
选项:[-v]显示详细模式。
2.4 modprobe命令
装载或卸载内核模块。
命令定义:modprobe [模块名称] [选项]
语法:modprobe [模块名称] [选项]
选项:[模块名称]指定要装载或卸载的模块。
2.5 depmod命令
内核模块依赖关系文件及系统信息映射文件的生成工具。
语法:depmod [-a] [-F file] [-e] [-n] [-N] [-v]
参数:[-a]生成所有模块的依赖关系,[-F file]指定依赖关系文件,鉴定网站源码[-e]仅显示错误信息,[-n]不生成依赖关系,[-N]不生成映射文件,[-v]显示详细模式。
2.6 insmod和rmmod命令
装载或卸载内核模块。
insmod命令:insmod [模块名称] [选项]
rmmod命令:rmmod [模块名称] [选项]
3. /proc目录
内核将自己内部状态信息、统计信息以及可配置参数通过proc伪文件系统输出。
3.1 sysctl命令
语法格式:sysctl [-n] [-e] [-f file] [-p] [-a] [-r] [-w] [name [...]]
命令参数:[-n]不打印数值,[-e]退出时显示错误,[-f file]指定配置文件,[-p]打印所有配置,[-a]显示所有参数,[-r]读取配置,[-w]写入配置,[name [...]]指定要设置的参数。
3.2 修改配置文件
3.3 实战演示
4. /sys目录
sysfs伪文件系统,输出内核识别出的各硬件设备的相关属性信息,以及内核对硬件特性的设定信息。有些参数可以修改,用于调整硬件工作特性。
4.1 udev
4.2 ramdisk文件的制作
方法一:使用dd命令
方法二:使用mkinitramfs命令
4.3 查看ramdisk
5. 编译内核
5.1 前提准备
(1) 准备好开发环境
(2) 获取目标主机上硬件设备的相关信息
(3) 获取到目标主机系统功能的相关信息
(4) 获取内核源代码包
5.2 简易安装内核
简易安装:简单依据模板文件的制作内核
5.3 详解编译内核
(1) 配置内核选项
(2) 编译 - make [-j #]
链接:blog.csdn.net/daocaokaf...
深入剖析Linux文件系统之文件系统挂载(一)(超详细~)
深入剖析Linux文件系统之文件系统挂载(一)(超详细~) 我们知道,在Linux系统中,将一个块设备上的文件系统挂载到特定目录才能访问该文件系统下的文件。本文将详细阐述文件系统挂载的核心逻辑,包括Linux内核为挂载文件系统所执行的操作以及为何必须挂载才能访问文件。本文分为上下两篇,上篇着重于挂载全貌及具体文件系统挂载方法,下篇则详细介绍挂载实例与挂载点、超级块的关系。 在Linux中,虚拟文件系统层VFS通过统一所有具体文件系统的接口,屏蔽差异,向用户提供一致的访问方式。VFS作为接口层,向下连接具体的文件系统,向上提供用户进程访问文件的功能。接下来,我们探讨VFS中几个关键对象的作用。 VFS对象包括: file_system_type:描述文件系统类型,包括磁盘文件系统、内存文件系统、伪文件系统和网络文件系统。磁盘文件系统用于非易失性存储介质上的文件,如ext2、ext4、xfs等;内存文件系统在内存上存储文件;伪文件系统则是内核可见或用户可见的虚拟文件系统,如proc、sysfs等;网络文件系统允许访问远程计算机上的数据。 super_block:用于描述块设备上文件系统整体信息,如文件块大小、最大文件大小、文件系统标识等。磁盘文件系统仅有一个super_block描述整个文件系统。 mount:描述超级块与挂载点之间的联系,建立文件系统挂载的实例。磁盘文件系统可被多次挂载,每次挂载内存中创建一个mount对象。 inode:描述磁盘上文件的元数据,文件系统需要从块设备读取磁盘上的inode,创建内存中的inode对象,通常在文件首次打开时创建。 dentry:用于描述文件层次结构,构建目录树,存储目录或文件的名称和inode号,以便进程访问目录项。 file:描述进程打开的文件,创建文件对象加入进程的文件打开表,通过文件描述符进行读写操作。 挂载流程包括系统调用处理、挂载点路径查找、参数合法性检查、调用具体文件系统挂载方法、以及实例添加到全局文件系统树。挂载实例添加到全局文件系统树涉及vfs_get_tree和do_new_mount_fc函数,ext2对挂载的处理则包括初始化阶段、挂载时调用、以及通过mount_bdev执行实际挂载工作。 具体文件系统挂载方法包括: ext2对挂载的处理:启动阶段初始化,挂载时调用ext2_mount,执行mount_bdev来执行实际挂载,ext2_fill_super读取磁盘上的超级块并填充内存中的超级块。 mount_bdev源码分析:查找块设备描述符,创建或获取vfs超级块,调用具体文件系统的fill_super方法读取并填充超级块。 ext2_fill_super源码分析:读取磁盘上的超级块,填充并关联vfs超级块,读取块组描述符,读取磁盘根inode并建立根inode,创建根dentry关联到根inode。 挂载完成后,文件系统已准备好被访问,用户进程通过文件路径打开文件,但尚未关联至挂载点。为了将文件系统关联到挂载点,需要通过do_new_mount_fc将挂载实例加入全局文件系统树。下篇将详细讲解这一过程。字符设备中的几个函数分析
1.在内核中, dev_t 类型(在 <linux/types.h>中定义)用来持有设备编号 — 主次部分都包括.其中dev_t 是 位的量, 位用作主编号, 位用作次编号
1 #ifndef _LINUX_TYPES_H
2 #define _LINUX_TYPES_H
3
4 #include <asm/types.h>
5
6 #ifndef __ASSEMBLY__
7 #ifdef __KERNEL__
8
9 #define DECLARE_BITMAP(name,bits) /
unsigned long name[BITS_TO_LONGS(bits)]
#endif
#include <linux/posix_types.h>
#ifdef __KERNEL__
typedef __u __kernel_dev_t;
typedef __kernel_fd_set fd_set;
typedef __kernel_dev_t dev_t; //用来持有设备编号的主次部分
typedef __kernel_ino_t ino_t;
typedef __kernel_mode_t mode_t;
...
2.在 <linux/kdev_t.h>中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:
2.1设备编号的内部表示
MAJOR(dev_t dev);
MINOR(dev_t dev);
2.在有主次编号时, 需要将其转换为一个 dev_t, 可使用:
MKDEV(int major, int minor);
在linux/kdev_t.h中有下了内容
...
4 #define MINORBITS
5 #define MINORMASK ((1U << MINORBITS) - 1)
6
7 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
8 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
9 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))//高为表示主设备号,低位表示次设备号
...
3.分配和释放设备编号register_chrdev_region函数
下面摘自文件fs/char_dev.c内核源代码
/
*** register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count; //计算分配号范围中的最大值+=
dev_t n, next;
for (n = from; n < to; n = next) { /*每次申请个设备号*/
next = MKDEV(MAJOR(n)+1, 0);/*主设备号加一得到的设备号,次设备号为0*/
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:/*当一次分配失败的时候,释放所有已经分配到地设备号*/
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
这里, from是要分配的起始设备编号. from 的次编号部分常常是 0, 但是没有要求是那个效果. count是你请求的连续设备编号的总数. 注意, 如果count 太大, 要求的范围可能溢出到下一个次编号;但是只要要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 不能存取请求的区域.
4.下面是char_device_struct结构体的信息
fs/char_dev.c
static struct char_device_struct {
struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
unsigned int major; // 主设备号
unsigned int baseminor; // 起始次设备号
int minorct; // 设备编号的范围大小
const char *name; // 处理该设备编号范围内的设备驱动的名称
struct file_operations *fops; // 没有使用
struct cdev *cdev; /* will die指向字符设备驱动程序描述符的指针*/
} *chrdevs[MAX_PROBE_HASH];
/
** Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/
/
*** 该函数主要是注册注册注册主设备号和次设备号
* major == 0此函数动态分配主设备号
* major > 0 则是申请分配指定的主设备号
* 返回0表示申请成功,返 回负数说明申请失败
*/
static struct char_device_struct
*__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{ /*以下处理char_device_struct变量的初始化和注册*/
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
//kzalloc()分配内存并且全部初始化为0,
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
//ENOMEM定义在include/asm-generic/error-base.h中,
// #define ENOMEM /* Out of memory */
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) { //下面动态申请主设备号
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i—) {
//ARRAY_SIZE是定义为ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
//#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
if (chrdevs[i] == NULL)
//chrdevs是内核中已经注册了的设备好设备的一个数组
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;//这里得到一个位使用的设备号
}
//下面四句是对已经申请到的设备数据结构进行填充
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;/*申请设备号的个数*/
strlcpy(cd->name, name, sizeof(cd->name));
/*以下部分将char_device_struct变量注册到内核*/
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major || //chardevs[i]设备号大于主设备号
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) || //chardevs[i]主设备号等于主设备号,并且此设备号大于baseminor
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
//在字符设备数组中找到现在注册的设备
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
/*所申请的设备好号能够满足*/
cd->next = *cp;/*按照主设备号从小到大顺序排列*/
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
以上程序大体上分为两个步骤:
1.char_device_struct类型变量的分配以及初始化~行
2.将char_device_struct变量注册到内核,行页到行
1.char_device_struct类型变量的分配以及初始化
(1)首先,调用 kmalloc 分配一个 char_device_struct 变量cd。
检查返回值,进行错误处理。
(2)将分配的char_device_struct变量的内存区清零memset。
(3)获取chrdevs_lock读写锁,并且关闭中断,禁止内核抢占,write_lock_irq。
(4)如果传入的主设备号major不为0,跳转到第(7)步。
(5)这时,major为0,首先需要分配一个合适的主设备号。
将 i 赋值成 ARRAY_SIZE(chrdevs)-1,其中的 chrdevs 是包含有个char_device_struct *类型的数组,
然后递减 i 的值,直到在chrdevs数组中出现 NULL。当chrdevs数组中不存在空值的时候,
ret = -EBUSY; goto out;
(6)到达这里,就表明主设备号major已经有合法的值了,接着进行char_device_struct变量的初始化。
设置major, baseminor, minorct以及name。
2.将char_device_struct变量注册到内核
(7)将 i 赋值成 major_to_index(major)
将major对取余数,得到可以存放char_device_struct在chrdevs中的索引
(8)进入循环,在chrdevs[i]的链表中找到一个合适位置。
退出循环的条件:
(1)chrdevs[i]为空。
(2)chrdevs[i]的主设备号大于major。
(3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor。
注意:cp = &(*cp)->next,cp是char_device_struct **类型,(*cp)->next是一个char_device_struct
*类型,所以&(*cp)->next,就得到一个char_device_struct **,并且这时候由于是指针,所以
对cp赋值,就相当于对链表中的元素的next字段进行操作。
(9)进行冲突检查,因为退出循环的情况可能造成设备号冲突(产生交集)。
如果*cp不空,并且*cp的major与要申请的major相同,此时,如果(*cp)->baseminor < baseminor + minorct,
就会发生冲突,因为和已经分配了的设备号冲突了。出错就跳转到ret = -EBUSY; goto out;
()到这里,内核可以满足设备号的申请,将cd链接到链表中。
()释放chrdevs_lock读写锁,开中断,开内核抢占。
()返回加入链表的char_device_struct变量cd。
()out出错退出
a.释放chrdevs_lock读写锁,开中断,开内核抢占。
b.释放char_device_struct变量cd,kfree。
c.返回错误信息
下面程序出自fs/char_dev.c
动态申请设备号
...
/
*** alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
/* dev:
仅仅作为输出参数,成功分配后将保存已分配的第一个设备编号。
baseminor:
被请求的第一个次设备号,通常是0。
count:
所要分配的设备号的个数。
name:
和所分配的设备号范围相对应的设备名称。
b.返回值:
成功返回0,失败返回负的错误编码
*/
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
...