magnificent0121
[TOC]
1、实验基本内容
本次实验共有四个任务,包括创建内核进程、打印输出当前系统CPU输出情况、打印输出当前处于运行状态的进程的PID和名字、使用cgroup实现限制CPU核数。
2、实验步骤及完成过程
2.1、环境的搭建
最初的搭环境,构建鲲鹏云ECS,编译安装openEuler操作系统新内核
因为是接触Linux的操作系统实验比较少,因此对在操作过程中遇到的重要的且陌生的名词进行了查询并总结
SSH是安全的加密协议,用于远程连接Linux服务器
SSH的默认端口是22,安全协议版本是SSH2
SSH服务器端主要包括2个服务功能SSH连接和SFTP服务器
SSH客户端包括ssh连接命令和远程拷贝scp命令等
随后,在输入密码时,注意到Linux系列的密码都是不显示的,不会像windows一样显示号。只要输入正确,点击回车即可进入。
接着,在修改主机名部分遇到了三个知识点,vi,cat和reboot
2.1.1、前置知识
vi 编辑器是所有Unix及Linux系统下标准的编辑器,基本操作是输入vi及文件名称后进入vi全屏幕编辑画面,然后注意到此时只是命令行模式(command mode),此时只有按下字母i才能进入插入模式(insert mode)进行编辑文件,在编辑完成后,先按一下ESC键转到命令行模式(command mode),再按shift和:进入底行模式(Last line mode),此时如果输入q,表明不保存直接退出,如果输入wq,则表明保存并退出。
(全拼:concatenate),cat命令是Linux下的一个文本输出命令,通常是用于观看某个文件的内容的;
cat主要有三个功能:
1、一次显示整个文件
cat filename
2、从键盘创建一个文件
cat > filename
只能创建新文件,不能编辑已有文件
3、将几个文件合并为一个文件
cat file1 file2 > file
3)reboot
用于重新启动计算机
接着开始
2.1.2、openEuler内核编译与安装
-
安装工具、构建开发环境;
-
备份boot目录以防后续步骤更新内核失败;
-
保存当前内核版本信息;
-
获取内核源代码并解压;
-
编译内核;
-
安装内核;
-
以VNC登录ECS;
以root身份登录:
2.2.3、创建内核进程
Linux下编程分为用户编程和内核模块编程,本次实验的前三个任务都是内核模块编程,最后一个是用户模块编程。
用户编程和内核模块编程的区别
应用程序 | 内核模块程序 | |
---|---|---|
使用函数 | libc库 | 内核函数 |
运行空间 | 用户空间 | 内核空间 |
运行权限 | 普通用户 | 超级用户 |
入口函数 | main() | module_init |
出口函数 | exit() | module_exit |
编译 | gcc -c | makefile |
链接 | gcc | insmod |
运行 | 直接运行 | insmod |
调试 | gdb | kdbug、kdb、kgdb |
2)一个Linux内核模块主要有以下几个部分组成。
1、 模块加载函数,用module_init()来指定
当通过insmod命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
//Linux模块加载函数一般以_init表示声明。典型声明如下:
static int _init initialization_function(void){
/* 初始化代码*/
}
module_init(initialization_function);
2、 模块卸载函数,用module_exit()来指定
当使用rmmod命令来卸载内核模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。
static void _exit cleanup_function(void){
/*释放代码*/
}
moudule_exit(cleanup_function);
3、 模块许可证声明
MODULE_LICENSE(“GPL”);
模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告。
printk: printk相当于printf的孪生姐妹,她们一个运行在用户态的,另一个则在内核态。
3)模块实用程序
-
insmod命令
调用 insmod程序把需要插入的模块以目标代码的形式插入到内核中。再插入的时候,insmod自动调用init_module()函数运行。
-
rmmod命令
调用rmmod程序将已经插入内核的模块从内核中移出,rmmod会自动运行cleanup_module()函数
-
lsmod命令
调用lsmod命令将显示当前系统中正在使用的模块信息。实际上这个程序的功能就是读取/proc文件系统中的文件/proc/modules中的信息。
-
ksyms命令
ksyms这个程序用来显示内核符号和模块符号表的信息。与lsmod相似,它的功能是读取/proc文件系统中的另一个文件/proc/kallsyms
2.2、
实验一要求我们创建内核进程
kthread.c的思路与过程
首先当通过insmod命令加载内核模块时,模块的加载函数kthread_init会自动被内核执行,首先执行printk()打印出"Create kernel thread!\n",接着调用kthread_run(),这个函数的功能是创建并启动一个线程,它的三个参数分别为要执行的线程函数,线程函数的参数,线程的名字。要执行的线程函数为print(),线程名字为mythread,线程函数的参数为NULL。最后,若初始化成功,则返回0.代码如下:
static int __init kthread_init(void)
{
printk("Create kernel thread!\n");
myThread = kthread_run(print, NULL, "new_kthread");
return 0;
}
于是在未卸载内核模块时,这个线程函数print()会一直运行直到接收到终止信号。因此函数中需要有判断是否收到信号的语句。kthread_should_stop()用于接收kthread_stop传递的结束线程函数。此外要主要到在线程函数中需要在每一轮迭代后休眠一定时间,让出CPU给其他的任务,否则创建的这个线程会一直占用CPU,使得其他任务军瘫痪。更严重的是,使线程终止的命令也无法执行,导致这种状况一直执行下去。msleep(2000);代码为:
static int print(void *data)
{
while(!kthread_should_stop()){
printk("New kthread is running.");
msleep(2000);
}
return 0;
}
接着若运行当使用rmmod命令来卸载内核模块时,模块的卸载函数kthread_exit()会自动被内核执行。此时执行printk(),输出"Kill new kthread.\n",接着执行kthread_stop(mythread)函数,作用是在模块卸载时,发送信息给mythread指向的线程,使之退出。注意到因为在调用kthread_stop函数时,线程不能已经结束运行,否则,kthread_stop()函数会一直等待。所以要加个if(mythread)保证kthread_stop()在线程运行时执行。
此时模块已经卸载,线程退出。
kthread.c的代码
接下来是编写Makefile文件,这个部分因为是第一次接触,所以感觉比较难以下手,查阅了许多资料,最后我找到了 https://seisman.github.io/how-to-write-makefile/index.html 一个Makefile经典教程,感觉收获很大。
前置知识
1)$(make)的含义
表示预定义的make这个命令的名称。可以使用
make -p
命令查看所有的预定义的变量。
$(VAR)是获取make file中的环境变量或者宏定义的值。
截图
2)ifneq的用法
ifneq($(变量名),变量值)
…
end if
如果第二个参数为空就是NULL
3)uname命令
uname命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)
语法:
uname (-r)
uname -r显示操作系统的发行编号
4)较难的两句代码
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
这里的KERNELDIR指的是内核库文件的路径,c中使用的是内核提供的函数,而这些函数也是有具体实现的,在连接成一个内核模块时要说明这些库文件在哪里,方便连接程序把它们连接成一个完整的模块。
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
-C是表示进入$(KERNELDIR)目录执行makefile,M=$(PWD)意思是返回到当前目录继续读入、执行当前的Makefile.
5)clean命令
.PHONY:clean
clean:
-rm *.mod.c *.o *.order *.symvers *.ko
.PHONY是一个伪目标,可以防止在Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种的提交执行makefile的效率
make clean
用于清除上次的make命令所产生的obbject文件和可执行文件。
Makefile的整个流程
make读取到当前目录下的Makefile文件,发现KERNELRELEASE未定义就去执行else部分,切换到KERNELDIR目录下,读取下面的Makefile文件(这是-C选项的作用),在进行模块编译前切换到PWD目录(这是M=..的作用);在PWD目录下编译模块时,make再次读取当前目录下的Makefile,这时候KERNELRELEASE变量已经定义(在KERNELDIR下的Makefile中定义),make会读取到
obj-m:=kthread.o
即指定当前目录要生成的目标模块。根据内核编译系统的规则,进行hello.c->hello.o->hello.ko的编译。
Makefile的代码
ifneq ($(KERNELRELEASE),)
obj-m := kthread.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
.PHONY:clean
clean:
-rm *.mod.c *.o *.order *.symvers *.ko
2)加载内核模块
加载编译完成的内核模块,并查看加载结果
3)卸载内核模块
卸载内核模块,并查看结果
实验一完成。
实验二是需要我们打印输出当前系统系统的CPU负载情况
CPU负载情况定义为在特定时间间隔内运行队列中的进程数量的平均值,也即处于运行或不可打扰状态的进程的平均数。
-
可运行:运行态,占用CPU,或就绪态,等待CPU调度
-
不可打扰:阻塞,正在等待I/O。
2)loadavg文件
Linux中在Proc文件系统的loadavg文件可以查看系统1分钟、5分钟、15分钟的平均负载情况.
3)strcpy()函数
strncpy(char *dest,const char *src,int n)
作用是将src所指向是字符串以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest).因为我们需要打印一分钟内的cpu负载,所以只需要前4个字节(1个int是4个字节)
2.3.2、cpu_loadavg.c
cpu_loadavg.c及Makefile与第一次的kthread.c及Makefile的编写格式很类似,所以现在只描述主要思路.
cpu_loadavg.c的思路与过程
首先加载模块执行cpu_loadavg_init(void),打印"Start cpu_loadavg!"后调用get_loadavg()函数打印CPU的负载情况。
get_loadavg()函数中的主要步骤是先执行file_open(),在kernel中打开/proc/下的loadavg文件,然后执行kernel_read()函数将loadavg内的信息读下来存到buf_cpu中,然后调用strncpy()将,然后关闭文件,返回0结束函数。
然后卸载模块执行cpu_loadavg_exit(void),打印"Exit cpu_loadavg!"结束。
所以cpu_loadavg.c的代码如下:
#include <linux/module.h>
#include <linux/fs.h>
MODULE_LICENSE("GPL");
char tmp_cpu_load[5] = {'\0'};
static int get_loadavg(void)
{
struct file *fp_cpu;
loff_t pos = 0;
char buf_cpu[10];
fp_cpu = filp_open("/proc/loadavg", O_RDONLY, 0);
if (IS_ERR(fp_cpu)){
printk("Failed to open loadavg file!\n");
return -1;
}
kernel_read(fp_cpu, buf_cpu, sizeof(buf_cpu), &pos);
strncpy(tmp_cpu_load, buf_cpu, 4);
filp_close(fp_cpu, NULL);
return 0;
}
static int __init cpu_loadavg_init(void)
{
printk("Start cpu_loadavg!\n");
if(0 != get_loadavg()){
printk("Failed to read loadarvg file!\n");
return -1;
}
printk("The cpu loadavg in one minute is: %s\n", tmp_cpu_load);
return 0;
}
static void __exit cpu_loadavg_exit(void)
{
printk("Exit cpu_loadavg!\n");
}
module_init(cpu_loadavg_init);
module_exit(cpu_loadavg_exit);
2.3.3、Makefile
Makefile与任务1的相比,只是换了.o的名字而已
Makefile的代码
ifneq ($(KERNELRELEASE),)
obj-m := cpu_loadavg.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
.PHONY:clean
clean:
-rm *.mod.c *.o *.order *.symvers *.ko
2.3.4、 实验步骤
1)编译源文件
加载编译完成的内核模块,并查看加载结果
卸载内核模块,并查看结果
实验二完成。
2.4、实验三
实验三是需要我们打印输出当前处于运行状态的进程的PID和名字
2.4.1、前置知识
1)task_struct
进程是处于执行期的
进程是处于执行期的程序以及他所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)总称。Linux内核通过一个被称之为进程描述符的task_stuct的结构体来管理进程,这个结构体包含了一个进程所需的所有信息。State的state成员为进程状态,pid为进程标识符,comm为进程名。
2)task_struct 的state
经查资料,state域能取五个互为排斥的值
分别为
其中第一个状态是正在占有cpu或者处于就绪状态的进程才能拥有。一旦某一个进程它的state域的值等于TASK_RUNNING,那么这个进程要么是正在运行,要么就是已经就绪,正在等待cpu时间片的调度,教科书上将正在CPU上执行的进程定义RUNNING状态,而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在linux下统一为TASK_RUNNING状态。
其余几个分别为:可执行状态、可中断的睡眠状态;不可中断的睡眠状态;暂停状态或跟踪状态;退出状态,进程成为僵尸进程;
3)for_each_process
for_each_process是一个宏,定义在<linux/sched/signal.h>文件中,提供了依次访问整个任务队列的能力。
Process_info.c及Makefile与第一次的kthread.o及Makefile的编写格式很类似,所以现在只描述主要思路.
process_info.c的思路与过程
首先加载模块执行process_info_init(void),打印"Start process_info!"后依次遍历整个任务队列,如果当前进程的state为0表示当前进程正在运行或就绪,则打印它的comm,pid与state.然后返回0结束函数。
然后卸载模块执行process_info_exit(void),打印"Exit process_info!"结束。
process_info.c的代码
#include <linux/module.h>
#include <linux/sched/signal.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
struct task_struct *p;
static int __init process_info_init(void)
{
printk("Start process_info!\n");
for_each_process(p){
if(p->state == 0)
printk("1)name:%s 2)pid:%d 3)state:%ld\n", p->comm, p->pid, p->state);
}
return 0;
}
static void __exit process_info_exit(void)
{
printk("Exit process_info!\n");
}
module_init(process_info_init);
module_exit(process_info_exit);
2.4.3、Makefile
Makefile与任务1,2的相比,只是换了.o的名字而已
Makefile的代码
ifneq ($(KERNELRELEASE),)
obj-m := process_info.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
.PHONY:clean
clean:
-rm *.mod.c *.o *.order *.symvers *.ko
2.4.4、实验步骤
加载编译完成的内核模块,并查看加载结果
卸载内核模块,并查看结果
实验三完成。
实验四是要求我们使用cgroup实现限制CPU核数
2.5.1、前置知识
free
free命令显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存。使用-h选项以KB、MB、GB的单位来显示、可读性高
查阅资料后,输出的内容如下:
-
Mem 行(第二行)是内存的使用情况。
-
Swap 行(第三行)是交换空间的使用情况。
-
total 列显示系统总的可用物理内存和交换空间大小。
-
used 列显示已经被使用的物理内存和交换空间。
-
free 列显示还有多少物理内存和交换空间可用使用。
-
shared 列显示被共享使用的物理内存大小。
-
buff/cache 列显示被 buffer 和 cache 使用的物理内存大小。
-
available 列显示还可以被应用程序使用的物理内存大小。
2)df -h命令
df -h
查看磁盘占用的空间
-
Filesystem:表示该文件系统位于哪个分区,因此该列显示的是设备名称;
-
Used:表示用掉的磁盘空间大小;
-
Available:表示剩余的磁盘空间大小;
-
Use%:磁盘空间使用率;
-
Mounted on:文件系统的挂载点,也就是磁盘挂载的目录位置
3)mount命令
加载指定的文件系统。
4)echo命令
显示文字。
依据线程PID(TID)查询或设置线程的CPU亲和性(即与哪个CPU核心绑定)。
在指定的cgroup中运行任务。即将进程限制在控制组群中运行。
挂载(mount)是将额外的文件系统与根文件系统某个现存的目录建立起关联关系,进而使得该目录作为其他文件访问入口的行为。通俗的说,挂载就是将某个未使用的空间或可移动设备的存储空间指向一个目录。这样,通过该目录就可以访问你的空间了。
挂载方法:
Mount device mount_point
其中devicec 是要挂载的设备名,而mount_point是挂载点,也就是最终能访问设备的目录。dir事先应存在,建议使用空目录。
top输出的
· 第一行:系统运行时间和平均负载
当前时间、系统已运行时间、当前登录用户的数量、最近5、10、15分钟内的平均负载
· 第二行:任务
任务的总数、运行中(running)的任务、休眠(sleeping)中的任务、停止(stopped)的任务、僵尸状态(zombie)的任务
· 第三行:cpu状态
· 第四行:内存
全部可用内存、已使用内存、空闲内存、缓冲内存
· 第五行:swap
全部、已使用、空闲和缓冲交换空间
· 第七行至N行:各进程任务的的状态监控
9)while_long.c
while_long.c的思路与过程
为使用cgroup限制cpu核数,需要写个死循环来验证,如果
While_long.c是个死循环,默认情况下cpu核数为4,而当通过cgexec命令将进程限制到cgroup中,使得其只能使用3个cpu核
while_long.c的代码
#include <stdlib.h>
int main(int argc, char *argv[])
{
while (1){}
printf("Over");
exit(0);
}
2.5.2、实验步骤
安装libcgroup
挂载tmpfs格式的cgroup文件
挂载cpuset管理子系统
3)设置cpu核数
1>打开一个新的终端,编译上述c文件并指定在cpuset子系统的mycpuset控制组中运行
2>再在另一个终端中,执行top命令验证测试。
实验四完成。
3、相关资料的URL:
1、Linux ssh命令详解:
https://www.cnblogs.com/ftl1012/p/ssh.html
2、Linux vi命令详解:
https://www.cnblogs.com/mahang/archive/2011/09/01/2161672.html
3、Linux cat命令详解
https://www.cnblogs.com/wanglinjie/p/9308577.html
4、Linux内核模块学习笔记
5、Linux内核模块编程
https://www.cnblogs.com/lrszs/p/3618493.html
6、跟我一起写Makefile
https://seisman.github.io/how-to-write-makefile/index.html
7、shell uname命令
https://blog.csdn.net/oshan2012/article/details/100653026
8、Makefile使用笔记
https://blog.csdn.net/woaiyouxiaojie/article/details/97888900
9、Linux系统命令make、clean的用法
https://www.cnblogs.com/xhqhome/p/5545147.html
10、
https://www.cnblogs.com/yangjiquan/p/11475638.html
11、make -C M选项
12、strncpy
https://baike.baidu.com/item/strncpy/8491017?fr=aladdin
13、Linux中进程的几种状态
https://blog.csdn.net/wangjianno2/article/details/44718401
14、Linux下free命令详解
https://www.cnblogs.com/ultranms/p/9254160.html
15、Linux的df -h命令
https://www.cnblogs.com/will-wu/p/12940320.html
16、Linux的tmpfs文件系统
https://www.cnblogs.com/3me-linux/p/4267421.html
17、
https://www.cnblogs.com/fuqu/p/10230385.html