本系列學(xué)習(xí)筆記基本上是博主的《 openEuler 操作系統(tǒng)》讀書筆記,中間插入一些自己查的資料以及翻到的感覺有用的源代碼
默認架構(gòu)為 ARM
程序及其加載執(zhí)行
類 UNIX 的二進制程序一般為 ELF 格式,一個【邏輯意義上作為整體的程序】會被按照內(nèi)容類型劃分為數(shù)個 Segment 進行存儲。主要的 Segment 有 :.text ,存機器指令序列;.data 存可變的全局變量及靜態(tài)局部變量;.rodata 存只讀數(shù)據(jù)、常量;.bss 存未初始化的全局變量。
加載,是操作系統(tǒng)將 ELF 讀入內(nèi)存的過程。首先檢查 ELF 頭,確定是否可以運行。然后讀端頭表,得到各個段的信息,為需要裝入內(nèi)存的段分配空間,然后把這些段加載到內(nèi)存中。
執(zhí)行。程序入口地址存在 ELF 頭中,OS 讀到該地址后到 .text 找到入口,將入口地址賦給 PC ,程序獲得 CPU 控制權(quán)。
運行過程中,PC 保存即將執(zhí)行的指令地址,LR(鏈接寄存器)保存函數(shù)調(diào)用返回后的下一條指令地址。
每個運行中的函數(shù)都擁有一個棧幀,其為函數(shù)的每次調(diào)用構(gòu)建獨立的上下文。當(dāng)函數(shù)調(diào)用新的函數(shù)時,新的函數(shù)會被分配新的棧幀。函數(shù)運行結(jié)束后棧幀會被彈出,系統(tǒng)從下面的棧幀(即發(fā)起對剛剛結(jié)束的函數(shù)調(diào)用的函數(shù)的棧幀)中取出之前保存的 FP、LR 值,繼續(xù)運行
進程的描述
操作系統(tǒng)使用 PCB (Process Control Block,進程控制塊)對進程進行描述,操作系統(tǒng)通過 PCB 感知進程。
PCB
PCB 定義在 include/linux/sched.h 的 struct task_struct,是一個六百二十多行的結(jié)構(gòu)體。
啟動一個程序時,操作系統(tǒng)先創(chuàng)建 PCB ,然后根據(jù)其中的信息對進程進行管理和控制,程序運行后系統(tǒng)釋放 PCB 。其中的主要信息包含描述信息、控制信息、 CPU 上下文、資源管理信息
描述信息
進程標(biāo)識符,OS 用它來標(biāo)記每個進程;
用戶標(biāo)識號,區(qū)分某個進程屬于哪個用戶
kuid_t loginuid;
unsigned int sessionid;
家族關(guān)系,表明該進程與其父進程、祖先進程、子進程、兄弟進程等的關(guān)系
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
real_parent 是創(chuàng)建該進程的進程,而 parent 是響應(yīng)信號相關(guān)的父進程——比如 SIGCHLD 就會被發(fā)送給 parent。這么設(shè)計是因為:有些情況下真父進程可能先終止,這樣如 init 這樣的其他進程就會成為新的父進程,但不會改變真父進程的值。
控制信息
進程狀態(tài)(就緒、阻塞、運行、終止)
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
/* 相關(guān)宏定義如下 */
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
進程優(yōu)先級
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
靜態(tài)優(yōu)先級:進程啟動時指定,值越小越高。一般不用,但可用系統(tǒng)調(diào)用 nice() 修改;
動態(tài)優(yōu)先級:可以因調(diào)度策略的改變而臨時變動;
實時:有限程度僅與實時優(yōu)先級有關(guān),值越小越高。實時進程調(diào)度總優(yōu)于普通進程
記賬信息:記錄進程占有、利用資源的情況,調(diào)度以這些信息為依據(jù)進行(如剝奪等),如時間方面、上下文切換方面的:
u64 utime;
u64 stime;
#ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME
u64 utimescaled;
u64 stimescaled;
#endif
u64 gtime;
struct prev_cputime prev_cputime;
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
struct vtime vtime;
#endif
#ifdef CONFIG_NO_HZ_FULL
atomic_t tick_dep_mask;
#endif
/* Context switch counts: */
unsigned long nvcsw;
unsigned long nivcsw;
/* Monotonic time in nsecs: */
u64 start_time;
/* Boot based time in nsecs: */
u64 real_start_time;
CPU 上下文
CPU 上下文指某時刻 CPU 各寄存器中的值,用于進程切換時保存狀態(tài)。其保存于 task_struct->thread_struct->cpu_context
從下面的代碼中可以看到,cpu_context 就是各個寄存器中的值的集合
/* arch/arm64/include/asm/processor.h */
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp;
unsigned long pc;
};
struct thread_struct {
struct cpu_context cpu_context; /* cpu context */
/* 省略其他變量,thread_struct 結(jié)構(gòu)體中包含所有與 CPU 相關(guān)的狀態(tài)信息 */
};
資源管理信息
這部分信息在 PCB 中占比最大,包含存儲器、文件系統(tǒng)、使用 IO 設(shè)備的信息等,主要與內(nèi)存和文件相關(guān)。
void *stack; // 指向進程內(nèi)核棧,內(nèi)核棧是進程在內(nèi)核態(tài)用的棧,在內(nèi)核空間
// 進程的用戶空間描述符
struct mm_struct *mm;
struct mm_struct *active_mm;
/* Filesystem information: */
struct fs_struct *fs; // 與進程相關(guān)的文件系統(tǒng)
/* Open file information: */
struct files_struct *files; // 進程正打開的文件列表
// 內(nèi)存描述符,定義在 include/linux/mm_types.h
struct mm_struct {
spinlock_t arg_lock; /* protect the below fields,自旋鎖 */
// 描述內(nèi)存中各個段的起始位置,包括棧、映射段、堆、BSS段、數(shù)據(jù)段、代碼段
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
//......
};
// 進程關(guān)聯(lián)的文件系統(tǒng)信息,include/linux/fs_struct.h
struct fs_struct {
int users; // 該結(jié)構(gòu)的引用用戶數(shù)
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd; // 根與當(dāng)前目錄
} __randomize_layout;
/* include/linux/fdtable.h
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count; // 引用計數(shù)
struct fdtable __rcu *fdt; // 默認指向 fdt,可用于動態(tài)申請內(nèi)存
struct fdtable fdtab; // 為 fdt 提供初始值
// fdt、fdtable 是管理文件描述符用的
// ...
};
// path. include/linux/path
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
} __randomize_layout;
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
進程的狀態(tài)
進程當(dāng)前狀態(tài)由 PCB 中的狀態(tài)值描述。這里的狀態(tài)就是運行、就緒、阻塞、終止(僵尸、死亡)
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED 0x0004
#define __TASK_TRACED 0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x0040
#define TASK_DEAD 0x0080
#define TASK_WAKEKILL 0x0100
#define TASK_WAKING 0x0200
#define TASK_NOLOAD 0x0400
#define TASK_NEW 0x0800
#define TASK_STATE_MAX 0x1000
就緒:進程位于運行隊列中,已獲得 CPU 外的所有資源,等待 OS 選中占用 CPU
運行:當(dāng)前進程主動放棄或被搶占,則轉(zhuǎn)為就緒;當(dāng)前進程等待資源或事件時,轉(zhuǎn)為阻塞;運行結(jié)束,進入終止?fàn)顟B(tài)(僵尸/等待)
阻塞:通常是等待外部事件(如 I/O ),需要等來了才就緒。但可被系統(tǒng)調(diào)用、信號等喚醒(阻塞分輕度中度深度,不同的程度需要的條件不同)
終止:
僵尸:父進程未回收進程及其占用的資源(包括 PCB )。若父進程先結(jié)束,init 會成為子進程的 parent ,待結(jié)束后回收資源
死亡:結(jié)束后由父進程回收資源