1 seq_file机制

1.1 seq_file原理

普通的文件没有一定的组织结构,文件可以从任意位置开始读写。有一种文件与普通文件不同,它包含一系列的记录,而这些记录按照相同的格式来组织,这种文件称为顺序文件(sequential file)。

struct seq_file { 
     char *buf; //缓冲 
     size_t size;  //大小 
     size_t from; 
     size_t count; 
     size_t pad_until;
     loff_t index; 
     loff_t read_pos; 
     u64 version; 
     struct mutex lock;  // 锁 
     const struct seq_operations *op;  //seq 操作 
     int poll_event; 
     void *private; 
};

seq_file常用操作接口如下:

int seq_open(struct file *, const struct seq_operations *);  //打开
int seq_read(struct file *, char __user*, size_t, loff_t *);  //读
loff_t seq_lseek(struct file *, loff_t, int);	// 定位
int seq_release(struct inode, struct file *);	// 释放
int seq_escape(struct seq_file *, const char *, const char *);	// 写缓冲
int seq_putc(struct seq_file *m, char c);	// 把一个字符输出到seq_file文件
int seq_puts(struct seq_file *m, const char s);	// 把一个字符串输出到seq_file文件

seq_operation结构是seq_file机制中所需要实现的操作接口。

struct seq_operation {
	void *(*start)(struct seq_file *m, loff_t *pos);	// 开始操作,返回pos指向记录
	void *(*stop)(struct seq_file *m, void *v);	// 关闭操作
	void *(*next)(struct seq_file *m, void *v, loff_t *pos);	// 寻找seq_file文件中的下一个记录
	int (*show)(struct seq_file *m, void*v);		//格式化输出记录信息
}

1.2 /proc文件系统

Linux内核中的/proc 文件系统是一种特殊的文件系统,通过它可以在运行时访问内核的内部数据结构、改变内核设置,内核可以通过它向进程发送信息。应用程序可以通过/proc 文件系统获取有关进程的有用信息,Linux中的ps、top命令就是通过读取/proc下的文件来获取它们需要的信息。
,/proc 主要存放由内核控制的状态信息,它存储于内存中而不是硬盘或其他存储设备上。/proc 文件系统的根目录就是/proc。对于系统中的任何一个进程来说,在proc的子目录里都有一个同名的进程ID。

用/proc 文件系统可以获取进程信息、电源管理(APM)信息、CPU信息(cpuinfo)、负载信息(loadavg)、系统内存信息(meminfo)等等。

fz@fz-VMware-Virtual-Platform:/proc$ ls
1     1690  2014  29    322   34    3614  371   384   4016  4279  48    57   79          consoles       misc
10    1691  2017  3     3220  340   3615  372   385   4024  4280  4804  58   8           cpuinfo        modules
100   17    2020  30    3221  341   362   3720  3852  4026  4281  4805  59   80          crypto         mounts
101   1714  2055  301   3222  342   3621  3727  3857  4040  43    4806  6    81          devices        mpt
102   1715  21    302   3223  345   3628  373   386   4049  4316  4838  60   82          diskstats      mtrr
103   1718  2127  303   3224  346   363   3732  387   41    4324  4839  61   83          dma            net
104   1721  2164  304   323   347   3634  3733  388   4109  4348  4840  62   84          driver         pagetypeinfo
105   1723  2176  306   324   348   364   3735  389   4111  4358  49    622  85          dynamic_debug  partitions
107   1724  22    307   325   349   365   3736  39    4113  437   493   623  86          execdomains    pressure
108   1736  2218  308   326   35    3653  3737  390   4114  438   4982  625  87          fb             schedstat
109   1738  23    309   327   350   366   3739  391   4132  4393  4989  63   88          filesystems    scsi
11    174   2367  31    328   352   3665  374   3915  4160  44    4995  630  89          fs             self
111   1748  2368  3102  329   353   3667  3740  392   4196  4403  5     64   9           interrupts     slabinfo
12    1757  2371  311   3291  3538  367   375   393   42    4449  50    65   90          iomem          softirqs
121   1767  2372  312   33    3539  3675  3755  3930  421   4450  5000  66   91          ioports        stat
1258  177   2379  313   330   354   368   3758  394   422   4451  5005  67   92          irq            swaps
1259  1797  24    314   3301  3540  3681  376   395   4266  45    5011  68   93          kallsyms       sys
1265  18    2446  315   3308  3548  3682  3760  396   4267  4527  51    69   95          kcore          sysrq-trigger
1266  181   2447  316   331   355   3683  3762  397   4268  4531  52    7    96          key-users      sysvipc
1274  1866  2448  317   332   356   3684  377   3973  4269  4538  525   70   97          keys           thread-self
1281  1879  2461  3176  333   3562  3686  378   398   4270  4594  529   71   98          kmsg           timer_list
1283  1882  25    318   3335  3564  369   379   399   4271  46    53    72   99          kpagecgroup    tty
13    19    2590  3187  334   357   3690  3792  3991  4272  4660  530   73   acpi        kpagecount     uptime
14    1926  2591  319   335   358   3695  38    4     4273  47    532   74   asound      kpageflags     version
15    196   26    32    336   359   37    380   40    4274  4722  54    749  bootconfig  latency_stats  version_signature
16    199   2603  320   337   3590  370   381   400   4275  4728  545   75   buddyinfo   loadavg        vmallocinfo
168   2     2654  321   3375  36    3703  382   4000  4276  4735  55    76   bus         locks          vmstat
1687  20    27    3215  338   360   3705  3825  401   4277  476   551   77   cgroups     mdstat         zoneinfo
1689  2012  28    3216  339   361   3707  383   4011  4278  4763  56    78   cmdline     meminfo
fz@fz-VMware-Virtual-Platform:/proc$ 

1.3 /proc文件系统接口

/proc文件系统的条目用proc_dir_entry结构描述

struct proc_dir_entry {
	unsigned int low_ino;
	umode_t mode;
	nlink_t nlink;
	kuid_t uid;	//用户
	kgid_t gid;	//用户组
	loff_t size;
	const struct inode_operations *proc_iops;	// 节点操作
	const struct file_operations *proc_fops;		// 文件操作
	struct proc_dir_entry *parent;						// 父路径
	struct rb_root subdir;
	struct rb_node subdir_node;
	void *data;
	atomic_t count;			// 使用计数
	atomic_t in_use;			//当前调用者数量
	void *data;					
	atomic_t count;
	atomic_t in_use;
	struct completion *pde_unload_completion;
	struct list_head pde_openers; /* who did ->open, but not ->release*/
	spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
	u8 namelen;
	char name[];
}

下面是几个重要的/proc文件系统接口函数。

// 该函数用于创建一个 proc 目录
// name 指定要创建的 proc 目录的名称
// parent为该 proc 目录所在的目录
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);

// proc_create 函数用来创建 proc 条目
// name 为文件名称。mode 为文件权限。
// parent为文件的父目录的指针,它为 null 时表示父目录为/proc
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,  const struct file_operations *proc_fops);

// proc_create_data 函数只比 proc_create 函数多了一个 data 参数,用于填充 proc_dir_entry结构的 data 成员
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, 
struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data);

// proc_remove 函数用于删除 de 指向的 proc 条目
void proc_remove(struct proc_dir_entry *de);

// remove_proc_entry 函数用于删除 parent 目录下名为 name 的 proc 条目。parent 为 NULL表示父目录为/proc
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

下面的例子演示了如何创建/proc 文件系统的节点,并对其进行读写操作。

static const struct file_operations proc_simple_fops = {
    .owner = THIS_MODULE,
    .write = simple_write,
    .read = seq_read,
    .open = simple_proc_open,
    .release = single_release,
};
int init_simple_module(void)
{
    int ret = 0;
    simple_buffer = (char *)vmalloc(MAX_simple_LENGTH);
    if (!simple_buffer)
    {
        ret = -ENOMEM;
    }
    else
    {
        memset(simple_buffer, 0, MAX_LENGTH);
        proc_entry = proc_mkdir("demo", NULL);
        proc_status = proc_create("status", 0, proc_entry, &proc_simple_fops);
        if (!proc_status)
        {
            ret = -ENOMEM;
            vfree(simple_buffer);
            printk(KERN_INFO "demo: Couldn't create proc entry\n");
        }
        else
        {
            printk(KERN_INFO "demo: Module loaded.\n");
        }
    }
    return ret;
}
void cleanup_simple_module(void)
{
    proc_remove(proc_entry);
    vfree(simple_buffer);
    printk(KERN_INFO "demo: Module unloaded.\n");
}

以下是打开操作的实现

static int simple_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, simple_show, inode->i_private);
}

以下是读操作的实现,它实现了打印系统当前进程信息的功能

static int simple_show(struct seq_file *file, void *iter)
{
    struct task_struct *p;
    char state;
    seq_printf(file, "%5s%7s%7s%7s%7s%7s%7s %s\n\n",
               "PID", "UID", "PRIO", "POLICY", "STATE", "UTIME", "STIME", "COMMAND");
    for_each_process(p)
    { // 遍历进程
        int pid = p->pid;
        if (unlikely(!pid))
            continue;
        switch ((int)p->state)
        {
        case -1:
            state = 'Z';
            break;
        case 0:
            state = 'R';
            break;
        default:
            state = 'S';
            break;
        }
        seq_printf(file, "%5d%7d%7d%7d%7c%7d%7d %s\n",
                   (int)p->pid, (int)p->tgid, (int)p->rt_priority,
                   (int)p->policy, state, (int)p->utime, (int)p->stime, p->comm);
    }
    return 0;
}

以下是写操作的实现代码:

ssize_t simple_write(struct file *flip, const char __user *buff, size_t len, loff_t *offset)
{
    if
        (len > MAX_LENGTH)len = MAX_LENGTH; if (copy_from_user(simple_buffer, buff, len )) {
            return -EFAULT; } simple_buffer[len] = 0; printk(KERN_INFO "simple_write: %s\n",simple_buffer);

            return len;
}

本例运行如下:

insmod demo.ko
cat /proc/demo/status

2. Linux 内核导读

2.1 Linux 内核组成

Linux 内核主要由五个部分组成:进程调度、内存管理、文件系统、网络子系统、设备管理。

(1)进程调度(Process Schedule):Linux 支持多任务与多进程,Linux 内核调度器基于优先级可动态调整的进程调度器。Linux 进程的通信支持系统 V 的各种通信机制。Linux 内核从 2.6.23 版本开始采用 CFS(Completely Fair Scheduler)调度器。CFS 的主要理念是为任务提供处理器时间方面的公平性,确保每个任务能够有时间执行。CFS 采用红黑树思想来管
理进程调度。
(2)内存管理(Memory Management):Linux 的内存管理支持虚拟内存,它采取的是分页机制。内存管理子系统允许多个进程安全地共享主内存区域。通过内存管理,Linux 可支持超过实际内存大小的内存地址,磁盘可以被用作内存,磁盘与内存之间可以相互交换。嵌入式处理器中的 MMU 单元就是用来实现内存管理的,虚拟地址到物理地址的映射就是由MMU 单元完成的。
(3)文件系统(Filesystem):Linux 内核支持多种文件系统,在 Linux 操作系统中,用户甚至可以访问 Windows 系统的文件,这得益于 Linux 内核中的虚拟文件系统(简称VFS)。VFS 使用一个通用的文件模型来管理不同的文件系统,为所有的存储设备提供了统一的接口。VFS 支持的文件系统包括 ext2、ext3、ext4、fat、jffs2、ubifs 等,多达数十种。
(4)设备管理(Device Management):Linux 支持字符设备、块设备及网络设备三类硬件设备,并提供了平台设备的概念与 sys 文件系统来管理各种设备。Linux 的设备驱动可以编译进内核,在系统启动时加载,也可以作为模块形式动态地加载。从 2.6 版内核开始,Linux 提供了统一的内核设备模型,这个模型的最高层抽象为 Kobject,这个数据结构使所有设备在底层都具有统一的接口。
(5)网络子系统(Network Subsystem):对网络协议的支持从一开始就是 Linux 的重要特性。Linux 网络子系统包括网络协议部分与网络驱动程序。网络协议部分负责实现各种网络协议,而网络设备驱动程序负责与物理网卡通信。在应用层,Linux 支持网络套接字接口。
正因为 Linux 具有强大的网络子系统,目前 Linux 在网络服务器、交换与路由设备中的应用非常广阔。

2.2 Linux 的代码结构

Linux 的代码结构

arch            硬件平台相关代码
block           块设备核心代码
crypto          加密函数库
documentation   有关内核各个部分的通用解释和注释的文本文件
drivers         设备驱动相关代码
fs              文件系统相关代码
include         内核头文件
init            内核初始化代码
ipc             System V 的进程间通信
kernel          内核核心部分:进程调度、中断处理、信号处理、模块
lib             通用内核函数
mm              内存管理
net             网络通信协议代码
samples         内核例子
security        系统安全相关代码
sound           音频体系代码

编译内核的几个命令如下所示

make menuconfig //配置内核命令
make //编译生成目标文件,包括可加载模块
make zImage //编译生成内核
make modules_install //安装模块

当需要将模块安装到非默认位置的时候,可以使用 INSTALL_MOD_PATH 指定一个前缀

make INSTALL_MOD_PATH=/foo modules_install

运行这个命令后模块将被安装到 /foo/lib/modules 目录下

2.3 内核 Makefile

Linux 内核的 Makefile 分为 4 个组成部分:
(1)顶层 Makefile:在内核代码最顶层。顶层的 Makefile 文件读取 .config 文件的内容,并总体上负责 build 内核和模块。arch 目录的 Makefile 提供了硬件体系结构相关的编译信息。Scripts 目录下的 Makefile 文件包含了所有用来根据 kbuild Makefile 构建内核所需的定义和规则。
(2)config 配置文件:内核的配置文件,一般在/arch/*/configs 下面。
(3)Makefile 的通用规则:在 scripts/目录下的 Makefile.*文件中。
(4)kbuild Makefile 文件:在各级目录下面。
kbuild Makefile 的语法结构非常简单,主要包括以下几点核心内容:
1)目标定义
目标定义就是用来定义哪些内容要作为模块编译,哪些内容要编译链接进内核。例如:

obj-y += foo.o

它表示要由 foo.c 或者 foo.s 文件编译得到 foo.o 并链接进内核。如果使用$(obj-m)则表示对象文件编译成可加载的内核模块。kbuild Makefile 文件中的目标通常是下面的形式:

obj-$(CONFIG_I2C_BOARDINFO)+= i2c-boardinfo.o 
obj-$(CONFIG_I2C)+= i2c-core.o 
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o

上面的语句告诉编译器编译选项对应的目标文件。
2)多文件模块的定义
如果一个模块由多个文件组成,则采用模块名加 -objs 后缀或者 -y 后缀的形式来定义模块的组成文件。例如:

obj-$(CONFIG_FB) += fb.o 
fb-y:= fbmem.o fbmon.o fbcmap.o fbsysfs.o \ 
 modedb.o fbcvt.o 
fb-objs:= $(fb-y)

上例中目标模块名为 fb,它依赖于 fbmem.o、fbmon.o、fbcmap.o、fbsysfs.o、modedb.o、
fbcvt.o 等文件,这些文件最终链接生成 fb.ko 文件。上面的斜杠\表示换行。
3)目录迭代
目录迭代是将目标依赖的文件指向另一个目录。

obj-$(CONFIG_FB_OMAP) += omap/

如果 CONFIG_FB_OMAP 的值为 y 或 m,kbuild 会将 omap 目录列入向下迭代的目标
中,但是其作用也仅限于此,至于 omap 目录下的文件是要作为模块编译还是链接入内核,
还要由 omap 目录下的 Makefile 文件的内容来决定。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐