Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件

Unix/Linux操作系统分析实验一 进程控制与进程互斥

​​​​​​Unix/Linux操作系统分析实验二 内存分配与回收:Linux系统下利用链表实现动态内存分配

Unix/Linux操作系统分析实验四 设备驱动: Linux系统下的字符设备驱动程序编程

本文章用于记录自己所学的内容,方便自己回顾复习。

实验内容

内容一:编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world。

/proc 文件系统简介:

    /proc 文件系统是Linux上的一种虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以更改其中某些文件来改变内核的运行状态。最初开发 /proc 文件系统是为了提供有关系统中进程的信息。但是由于这个文件系统非常有用,因此内核中的很多元素也开始使用它来报告信息或启用动态运行时配置。

图 1 是对 /proc 中部分元素进行一次交互查询的结果。它显示的是 /proc 文件系统的根目录中的内容。注意,在左边是一系列数字编号的文件。每个实际上都是一个目录,表示系统中的一个进程。由于在 GNU/Linux 中创建的第一个进程是 init 进程,因此它的 process-id 为 1。然后对这个目录执行一个 ls 命令,这会显示很多文件。每个文件都提供了有关这个特殊进程的详细信息。/proc 中另外一些有趣的文件有:cpuinfo,它标识了处理器的类型和速度;pci,显示在 PCI 总线上找到的设备;modules,标识了当前加载到内核中的模块。

这些文件的解释和意义如下:

cmdline:系统启动时输入给内核命令行参数

cpuinfo:CPU的硬件信息 (型号, 家族, 缓存大小等) 

devices:主设备号及设备组的列表,当前加载的各种设备(块设备/字符设备)

dma:使用的DMA通道

filesystems:当前内核支持的文件系统,当没有给 mount(1) 指明哪个文件系统的时候, mount(1) 就依靠该文件遍历不同的文件系统

interrupts :中断的使用及触发次数,调试中断时很有用

ioports I/O:当前在用的已注册 I/O 端口范围

kcore:该伪文件以 core 文件格式给出了系统的物理内存映象(比较有用),可以用 GDB 查探当前内核的任意数据结构。该文件的总长度是物理内存 (RAM) 的大小再加上 4KB

kmsg:可以用该文件取代系统调用 syslog(2) 来记录内核日志信息,对应dmesg命令

kallsym:内核符号表,该文件保存了内核输出的符号定义, modules(X)使用该文件动态地连接和捆绑可装载的模块

loadavg:负载均衡,平均负载数给出了在过去的 1、 5,、15 分钟里在运行队列里的任务数、总作业数以及正在运行的作业总数。

locks:内核锁 。

meminfo物理内存、交换空间等的信息,系统内存占用情况,对应df命令。

misc:杂项 。

modules:已经加载的模块列表,对应lsmod命令 。

mounts:已加载的文件系统的列表,对应mount命令,无参数。

partitions:系统识别的分区表 。

slabinfo:sla池信息。

stat:全面统计状态表,CPU内存的利用率等都是从这里提取数据。对应ps命令。

swaps:对换空间的利用情况。

version:指明了当前正在运行的内核版本。

内容二:添加新的系统调用。

1、系统调用号在unistd.h文件中定义。内核中每个系统调用号都以__NR_mysyscall开头,例如,fork的系统调用号为__NR_fork。于是,我们的系统调用号为__NR _mysyscall。具体在arch/x86/include/asm/ unistd_32.h和/usr/include/asm/unistd_32.h文件中添加如下:

   ...

   #define __NR_dup3             330

   #define __NR_pipe2            331

   #define __NR_inotify init1    332

   #define __NR_mysyscall        333/* mysyscall系统调用号添加在这里*/

添加系统调用号后,系统才能把这个号作为索引去查找系统调用表sys_call_table中的相应表项。

Include. uapi/asm-generic/unistd.h

2、在系统调用表中添加相应表项

如前所述,系统调用处理程序system_call会根据EAX中的号到系统调用表sys_call_tabe中查找相应的系统调用服务例程,因此,必须把服务例程sys_mysyscall派加到系统调用表中。系统调用表位于汇编语言arch/x86/kernel/ syscall_table_32.S中:找不到了。

内容三:Linux操作系统原理与应用教材上的第八章 例8-1

查看超级块super_block数据结构中的数据,并分析超级块与分区、文件系统、索引节点的关系。

实验步骤

内容一:编写一个内核模块,在/proc文件系统中增加一个目录hello,并在这个目录中增加一个文件world,文件的内容为hello world的步骤:

(1)首先获取内核源码:

浏览器输入网址:http://mirrors.aliyun.com/linux-kernel/v4.x/linux-4.2.6.tar.xz——下载内核源码

(2)终端控制台输入以下命令:

xz –d  *.tar.xz——将内核源码解压为tar格式

tar –xvf  *.tar——将内核源码解压至当前目录

(3)通过函数proc_mkdir创建Hello目录;

(4)通过函数proc_create创建World文件;

(5)定义模块proc_hello的初始化和清理函数;

(6)编写Makefile;

(7)程序的运行与验证。

内容二:

添加系统调用的步骤:

1、实验前的准备操作

sudo apt-get update  //更新系统源码

sudo apt-get install libncurses5-dev libssl-dev  //下载依赖包

sudo apt-get install build-essential openssl 

sudo apt-get install zlibc minizip 

sudo apt-get install libidn11-dev libidn11

sudo apt-get install flex

sudo apt-get install bison

2、添加系统调用号。系统调用号在 unistd.h文件中定义,以“__NR_”开头;

3、在系统调用表(在 entry.S中)中添加自己的服务例程 sys_coutname;

在/linux-4.2.6/arch/x86/entry/syscalls/syscall_64.tbl中条加服务例程:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第1张图片

4、实现系统调用服务例程:把 sys_coutname加在 kernel目录下的系统调用文件sys.c中;

在/linux-4.2.6/arch/x86/include/asm/syscalls.h中添加如下函数声明:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第2张图片

在/linux-4.2.6/kernel中添加如下函数:

5、重新编译内核;

在终端命令行分别输入下面的命令:

sudo make mrproper

sudo make clean

sudo make menuconfig

sudo make -j12(j后面的数字表示核数,核数越大,编译越快)

sudo make modules_install 

sudo make install

完成后,reboot即可。

6、编写用户态程序。

内容三:

Linux操作系统原理与应用教材上的第八章 例8-1

1、查看变量的地址;

sudo cat /proc/kallsyms | grep super_blocks

sudo cat /proc/kallsyms | grep sb_lock

2、编译模块;

make

3、将模块插入内核中;

sudo insmod printValue.ko

4、查看系统消息;

sudo dmesg

5、从内核中移除模块。

sudo rmmod printValue

实验结果分析(截屏的实验结果,与实验结果对应的实验分析)

实验结果:

内容一:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第3张图片

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第4张图片

内容二:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第5张图片

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第6张图片

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第7张图片

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第8张图片Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第9张图片

内容三:查看超级块super_block数据结构中的数据

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第10张图片

实验分析:

(1)超级块与分区是一对一还是一对多的关系?超级块与文件系统是什么关系?

答:超级块与分区是一对一的关系;超级块与文件系统是一对一关系,一个超级块对应一个文件系统。

(2)超级块与索引结点是什么关系?

答:超级块与索引结点是一对多的关系。超级块是对一个文件系统的描述,索引结点是对一个文件物理属性的描述。超级块是我们寻找索引结点的唯一源头。操作文件必然需要获得其对应的索引结点,而获取索引结点是通过超级块操作表提供的read_inode()函数完成的。操作索引结点的低层次任务,如创建一个索引结点、释放一个索引结点,也是通过超级块操作表提供的有关函数完成的。

实验总结

  通过这次实验,我理解Linux虚拟文件系统的内容,学会如何在虚拟文件系统/proc中实现文件操作;理解什么是超级块、文件系统、分区和索引节点及它们之间的关系;学会如何添加新的系统调用,如何重新编译内核,如何进行系统调用的测试。

出现的问题

1、在终端窗口命令行执行命令sudo make -j12(j后数字代表核数,数字越大编译越快)时,出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第11张图片

查阅资料得知:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第12张图片

2、在终端窗口命令行执行命令sudo make出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第13张图片

解决方法:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第14张图片

3、在终端窗口命令行执行命令sudo make出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第15张图片

无解决方法:主要问题就是Ubuntu 20.04版本编译4.2版本内核的不兼容问题,因为20.04已经开始内置5.x的内核。

博客网址:linux编译内核出错-CSDN论坛

4、在终端窗口命令行执行命令sudo menuconfig出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第16张图片

解决方法:在终端窗口命令行分别输入命令sudo apt-get install bisonsudo apt-get install flex

5、在终端窗口命令行执行命令sudo make -j12出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第17张图片

解决方法:原因可能是gcc-9的一些更改出现了bug,重新安装一遍gcc-8

6、在终端窗口命令行执行命令sudo make -j12 出现下图错误:

Unix/Linux操作系统分析实验三 文件操作算法: 实现在/proc目录下添加文件_第18张图片

解决方法:终端窗口命令行分别输入

1、cd linux-5.2.6

2、vi .config

3、输入/CONFIG_SYSTEM_TRUSTED_KEYRING找到相应的地方

4、删除 debian/canonical-certs.pem

5、按下Esc键,输入:wq!保存

所有实验的源代码如下:

pro_hello.c

#include 
#include 
#include 
#include 
#include 
#include 

static struct proc_dir_entry* proc_parent;
int len, temp;
char* msg; //字符数组

static ssize_t read_proc(struct file* filp, char __user* buf, size_t count, loff_t* offp) {
	//该函数将文件内容通过msg复制给buf,以此实现文件内容的读取。
	//read_proc为实现读取文件内容的函数指针
	if (count > temp)
		count = temp;
	temp = temp - count;

	raw_copy_to_user(buf, msg, count);
	if (count == 0)
		temp = len;
	return count;
}

static const struct file_operations proc_fops = {
	//赋予文件world只读的属性
	.read = read_proc
};

void create_new_proc_entry(void) {
	//create a new directory named hello, and return a pointer point to this dir
	//创建一个名为hello的新目录,并返回指向该目录的指针
	proc_parent = proc_mkdir("hello", NULL); //创建一个目录hello

	if (!proc_parent) //创建目录失败
		printk(KERN_INFO "Error creating proc entry");

	//create a file named world, add read attribute to this file using proc_fops
	//创建一个名为world的文件,使用proc_fops将read属性添加到此文件

	//world文件的父目录指针即为先前创建hello目录所返回的指针
	proc_create("world", 0, proc_parent, (const struct proc_ops*)&proc_fops); //第一个参数为文件名,第二个参数为文件的读写权限,第三个参数为其父目录的结构体指针,第四个参数为文件的读写操作结构体。

	msg = "hello world\n"; //文件内容
	len = strlen(msg); //获得字符串msg的长度
	temp = len;
	printk(KERN_INFO "1.len = %d", len);
	printk(KERN_INFO "proc initialized");
}

int proc_init(void) { //模块的初始化函数
	create_new_proc_entry();
	return 0;
}

void proc_cleanup(void) { //模块的退出和清理函数
	printk(KERN_INFO " Inside cleanup_module\n");
	remove_proc_entry("hello", proc_parent); //移除目录hello
	remove_proc_entry("world", NULL); //移除文件world
}

module_init(proc_init); //向内核注册模块提供新功能
module_exit(proc_cleanup); //注销由模块提供所有的功能
MODULE_LICENSE("GPL"); //模块具有GUN公共许可证

MakeFile

ifneq ($(KERNELRELEASE),) 
	obj-m := proc_hello.o #产生proc_hello模块的目标文件
else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
	PWD := $(shell pwd)
default: 
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif 
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
	
CONFIG_MODULE_SIG=n
#shell uname -r: Linux内核源代码的当前版本
#PWD: 模块所在的当前路径

sys.c

#include 
#include 
#include 
int main() {
	unsigned long sys_num = 323;
	unsigned long value = 0;
	__asm__("int $0x80"
		:"=a"(value)
		:"0"((long)(sys_num)));
	//syscall((long)(323));
	printf("The value is %ld\n", value);
	return value;
}

Makefile

ifneq ($(KERNELRELEASE),) 
	obj-m := proc_hello.o #产生proc_hello模块的目标文件
else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
	PWD := $(shell pwd)
default: 
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif 
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
	
CONFIG_MODULE_SIG=n
#shell uname -r: Linux内核源代码的当前版本
#PWD: 模块所在的当前路径

如若侵权,可联系我,我会在看到消息的同时,删除侵权的部分,谢谢大家!

如果大家有疑问,可在评论区发表或者私信我,我会在看到消息的时候,尽快回复大家!

你可能感兴趣的:(linux,unix,算法)