綜合能力訓練:在樹莓派上動手寫一個小OS(6):實驗16-5:程式建立實驗
本文節選自《實驗指導手冊》第二版第16.7章
實驗指導手冊是奔跑吧Linux核心入門篇第二版配套實驗書,pdf版本已經release,可以免費下載和自由列印!
下載方法:
登陸“奔跑吧linux社群”微信公眾號,輸入“奔跑吧2”獲取下載地址。
本文是《奔跑吧Linux核心 入門篇》第二版中第16章的實驗16-5:程式實驗。我們在前面的實驗中已經完成了printk列印函式以及時鐘中斷的實驗了,接下來我們就可以來完成程式建立的實驗了。在本實驗裡,我們要研究程式是如何建立的,瞭解新建立的程式是如何執行的。在這個實驗裡,我們要完成fork()函式,看看傳說中的fork函式究竟是如何實現的。
實驗指導手冊 列印小Tips:
大家可以在某寶上隨便找一個便宜的列印店,列印B5+黑白即可!很便宜,20~30元,還包郵!
1.實驗目的
(1)瞭解程式控制塊的設計與實現。
(2)瞭解程式的建立/執行過程。
2.實驗要求
實現fork函式以建立一個程式,該程式一直輸出數字“12345”。
3.實驗提示
(1)設計程式控制塊。
(2)為程式控制塊分配資源。
(3)設計和實現fork函式。
(4)為新程式分配棧空間。
(5)看看新建立的程式是如何執行的。
4.實驗詳解
4.1 程式控制塊PCB
我們使用struct task_struct資料結構來描述一個程式控制塊。
struct task_struct {
enum task_state state;
enum task_flags flags;
long count;
int priority;
int pid;
struct cpu_context cpu_context;
};
State:表示程式的狀態。使用enum task_state列舉型別來列舉出程式的狀態,有執行狀態TASK_RUNNING、可中斷睡眠狀態TASK_INTERRUPTIBLE、不可中斷的睡眠狀態TASK_UNINTERRUPTIBLE、殭屍態TASK_ZOMBIE以及終止態TASK_STOPPED。
enum task_state {
TASK_RUNNING =
0,
TASK_INTERRUPTIBLE =
1,
TASK_UNINTERRUPTIBLE =
2,
TASK_ZOMBIE =
3,
TASK_STOPPED =
4,
};
Flags用來表示程式的某些標誌位。目前只用來表示程式是否為核心執行緒。
enum task_flags {
PF_KTHREAD =
1 <<
0,
};
-
Count用來表示程式排程用的時間片。
-
Priority用來表示程式的優先順序。
-
Pid用來表示程式的PID。
-
cpu_context用來表示程式切換時候的硬體上下文。
4.2 0號程式
BenOS的啟動流程是:上電->樹莓派韌體->BenOS彙編入口->BenOS kernel_main函式。這個過程,從程式的角度來看,可以看出是系統的“0號程式”。
我們需要對這個0號程式進行管理。0號程式也是需要有一個程式控制塊,以方便管理。下面使用INIT_TASK巨集來靜態初始化0號程式的程式控制塊。
/* 0號程式即init程式 */
#define INIT_TASK(task) \
{ \
.state =
0, \
.priority =
1, \
.flags = PF_KTHREAD, \
.pid =
0, \
}
另外,我們還需要為0號程式分配棧空間。通常的做法是把0號程式的核心棧空間連結到資料段裡。注意,這裡僅僅是0號程式是這麼做的,其他程式的核心棧是動態分配的。
我們首先使用task_union的方式來定義一個核心棧。
/*
* task_struct資料結構儲存在核心棧的底部
*/
union task_union {
struct task_struct task;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
這樣,定義了一個核心棧的框架,在核心棧的底部,用來儲存程式控制塊。
目前我們的BenOS還比較簡單,所以核心棧的大小定義為一個頁面大小,即4KB。
/* 暫時使用1個4KB頁面來當作核心棧*/
#define THREAD_SIZE (
1 * PAGE_SIZE)
#define THREAD_START_SP (THREAD_SIZE -
8)
對於0號程式,我們把核心棧放到.data.init_task段裡。下面通過GCC的 attribute屬性來完成編連結,把task_union編譯連結到.data.init_task段中。
/* 把0號程式的核心棧 編譯連結到.data.init_task段中 */
#define __init_task_data __attribute__((__section__(
".data.init_task")))
/* 0號程式為init程式 */
union task_union init_task_union __init_task_data = {INIT_TASK(task)};
另外,我們還需要在BenOS的連結檔案benos.lds.S中新增一個名為.data.init_task段。修改arch/arm64/kernel/benos.lds.S檔案,在資料段中新增.data.init_task段。
4.3 do_fork函式的實現
本實驗需要實現do_fork函式,該函式的功能是為了fork一個新程式。
-
新建一個task_strut,為期分配4KB頁面用來儲存核心棧, task_struct存在棧底。
-
為新程式分配PID。
-
設定程式的上下文。
int do_fork(unsigned long clone_flags, unsigned long fn, unsigned long arg)
{
struct task_struct *p;
int pid;
p = (struct task_struct *)get_free_page();
if (!p)
goto error;
pid = find_empty_task();
if (pid <
0)
goto error;
if (copy_thread(clone_flags, p, fn, arg))
goto error;
p->state = TASK_RUNNING;
p->pid = pid;
g_task[pid] = p;
return pid;
error:
return
-1;
}
其中:
-
get_free_page()分配一個物理頁面,用於程式的核心棧。
-
find_empty_task()查詢一個空閒的PID。
-
copy_thread()用於設定新程式的上下文。
copy_thread()函式也是實現在kernel/fork.c檔案裡。
/*
* 設定子程式的上下文資訊
*/
static int copy_thread(unsigned long clone_flags, struct task_struct *p,
unsigned long fn, unsigned long arg)
{
struct pt_regs *childregs;
childregs = task_pt_regs(p);
memset(childregs,
0, sizeof(struct pt_regs));
memset(&p->cpu_context,
0, sizeof(struct cpu_context));
if (clone_flags & PF_KTHREAD) {
childregs->pstate = PSR_MODE_EL1h;
p->cpu_context.x19 = fn;
p->cpu_context.x20 = arg;
}
p->cpu_context.pc = (unsigned long)ret_from_fork;
p->cpu_context.sp = (unsigned long)childregs;
return
0;
}
PF_KTHREAD標誌位表示新建立的程式為核心執行緒,這時候pstate儲存了將要執行的模式為PSR_MODE_EL1h,x19儲存了核心執行緒的回撥函式,x20儲存了回撥函式的引數。
設定pc暫存器為ret_from_fork,即指向ret_from_fork彙編函式。設定sp暫存器指向棧的pt_regs棧框。
4.4 程式上下文切換
BenOS裡的程式上下文切換函式為switch_to(),用來切換到next程式來執行。
void switch_to(struct task_struct *next)
{
struct task_struct *prev = current;
if (current == next)
return;
current = next;
cpu_switch_to(prev, next);
}
其中核心的函式為cpu_switch_to()函式,其目的為儲存prev程式的上下文,並且恢復next程式的上下文,函式原型為:
cpu_switch_to(struct task_struct *prev, struct task_struct *next);
cpu_switch_to()函式實現是在arch/arm64/kernel/entry.S檔案裡。需要儲存的上下文,包括:x19 ~ x29暫存器,sp暫存器以及 lr暫存器,並且儲存到程式的task_struct->cpu_context。
.align
.global cpu_switch_to
cpu_switch_to:
add x8, x0, #THREAD_CPU_CONTEXT
mov x9, sp
stp x19, x20, [x8], #
16
stp x21, x22, [x8], #
16
stp x23, x24, [x8], #
16
stp x25, x26, [x8], #
16
stp x27, x28, [x8], #
16
stp x29, x9, [x8], #
16
str lr, [x8]
add x8, x1, #THREAD_CPU_CONTEXT
ldp x19, x20, [x8], #
16
ldp x21, x22, [x8], #
16
ldp x23, x24, [x8], #
16
ldp x25, x26, [x8], #
16
ldp x27, x28, [x8], #
16
ldp x29, x9, [x8], #
16
ldr lr, [x8]
mov sp, x9
ret
4.5 新程式的第一次執行
在程式切換時,switch_to()函式會完成程式硬體上下文的切換,即把下一個程式(next程式)的cpu_context資料結構儲存的內容恢復到處理器的暫存器中,從而完成程式的切換。此時,處理器開始執行next程式了。根據PC暫存器的值,處理器會從ret_from_fork彙編函式裡開始執行,新程式的執行過程如圖所示。
圖 新程式的執行過程
ret_from_fork彙編函式實現在arch/arm64/kernel/entry.S檔案中。
1 .align
2
2 .global ret_from_fork
3 ret_from_fork:
4 cbz x19,
1f
5 mov x0, x20
6 blr x19
7
1:
8 b ret_to_user
9
10 .global ret_to_user
11 ret_to_user:
12 inv_entry
0, BAD_ERROR
在第4行中,判斷next執行緒是否為核心執行緒。如果next程式是核心執行緒,在建立時會設定X19暫存器指向stack_start。如果X19的值暫存器為0,說明這個next程式是使用者程式,直接跳轉到第6行,呼叫ret_to_user彙編函式,返回使用者空間。不過我們這裡ret_to_user函式並沒有實現。
在第4~6行中,如果next程式是核心執行緒,那麼直接跳轉到核心執行緒的回撥函式裡。
綜上所述,當處理器切換到核心執行緒時,它從ret_from_fork彙編函式開始執行。
5.實驗步驟
我們先在QEMU上模擬。由於本實驗的參考程式碼還沒有實現對GIC-400中斷控制器的支援,因此,在QEMU裡我們只能模擬樹莓派3b了。
在Ubuntu Linux主機中,進入參考實驗程式碼目錄。
rlk@rlk :$ cd /home/rlk/rlk/runninglinuxkernel_5
.0/kmodules/rlk_lab/rlk_basic/chapter_16_benos/lab05_add_fork/
進入make menuconfig選單。
rlk@master:lab05_add_fork $ make menuconfig
其中:
Board selection (Raspberry
3B) -> 選樹莓派
3b
Uart
for Pi (pl_uart) -> 選PL串列埠
編譯並執行。
rlk@master:lab05_add_fork $ make
rlk@master: lab05_add_fork $ make run
6. 實驗參考程式碼
實驗參考程式碼runninglinuxkenrel_5.0的git repo。
國內訪問:
https:
//benshushu.coding.net/public/runninglinuxkernel_5.0/runninglinuxkernel_5.0/git/files
github(國外訪問):
https:
//github.com/figozhang/runninglinuxkernel_5.0
本文對應的參考程式碼在如下目錄:kmodules/rlk_lab/rlk_basic/chapter_16_benos/lab05_add_fork
我們提供配置好的實驗環境,基於ubuntu 20.04的VMware/Vbox映象,可以通過如下方式獲取下載地址:
登陸“奔跑吧linux社群”微信公眾號,輸入“奔跑吧2”獲取下載地址。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70005277/viewspace-2872537/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 綜合能力訓練:在樹莓派上動手寫一個小OS(4):實驗16-3:實現printk函式樹莓派函式
- 綜合能力訓練:在樹莓派上動手寫一個小OS(2):實驗16-1:輸出welcome benos樹莓派
- 綜合實驗
- 樹莓派上利用Tensorflow實現小車的自動駕駛樹莓派自動駕駛
- OSPF 綜合實驗
- OSPF綜合實驗
- BGP綜合實驗
- 在樹莓派上安裝 Ubuntu MATE樹莓派Ubuntu
- 靜態路由綜合實驗路由
- 在樹莓派上執行 DOS 系統樹莓派
- 綜合實驗,策略路由(BFD,NAT)路由
- 靜態路由及綜合實驗路由
- 計算機實驗室之樹莓派計算機樹莓派
- 資料結構實驗三:線性表綜合實驗資料結構
- 在樹莓派上搭建智慧家居閘道器樹莓派
- OpenYurt 入門 - 在樹莓派上玩轉 OpenYurt樹莓派
- 計算機網路實驗三 綜合性訓練(搭建中小企業園區網)計算機網路
- Linux備份任務綜合實驗Linux
- 樹莓派 - 實戰篇 [基於 websocket 實現手機遠端控制樹莓派小車]樹莓派Web
- 組合語言-實驗10編寫子程式組合語言
- 010.OpenShift綜合實驗及應用
- 關於在windows,ubuntu,樹莓派上安裝使用opencvWindowsUbuntu樹莓派OpenCV
- 樹莓派桌面體驗樹莓派
- 實驗6
- 實驗 6
- 樹莓派使用入門:在樹莓派上使用 Mathematica 進行高階數學運算樹莓派
- 在樹莓派上部署yolo模型推理並使用onnx加速樹莓派YOLO模型
- 綜合實訓週報八
- 程式設計實踐(Pandas)綜合練習1程式設計
- 物件導向綜合訓練物件
- 一個關於JAVA GC的小實驗JavaGC
- 使用 Ansible 在樹莓派上構建一個基於 Linux 的高效能運算系統樹莓派Linux
- 在樹莓派上安裝c++版本的opencv並執行樹莓派C++OpenCV
- 在樹莓派上開發SpringBoot 之使用VSCode遠端開發樹莓派Spring BootVSCode
- 在樹莓派上設定家庭網路的家長控制功能樹莓派
- SAP CDS redirect view支援寫操作嗎,一個實驗來驗證View
- SCAU 高程綜合實驗:檔案操作與字元處理字元
- 20個實驗之實驗10