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 filename2、從鍵盤創建一個文件
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

