賀邦
原創做品轉載請註明出處
《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000node
一、進程狀態、將紀錄進程在等待、運行、或死鎖 二、調度信息、由哪一個調度函數調度、怎樣調度等 三、進程的通信情況 四、有插入進程鏈表的相關操做,所以必須有鏈表鏈接指針、固然是task_struct型 五、時間信息,好比計算好執行的時間、以便CPU資源的分配 六、標號,決定改進程歸屬 七、能夠讀寫打開的一些文件信息 八、進程上下文和內核上下文 九、處理器上下文 十、內存信息等等
課堂提供在/linux-3.18.6/include/linux/sched.h中找到tast_struct的定義:
如今的Linux系統基本上是按照操做系統理論來進行設計的,可是在實現的過程當中,理論每每是不夠的,爲了實現不少實際的需求,tast_struct還定義了不少額外的結構,來方便系統的相關管理,好比後面沒有列出來的一些文件操做相關的結構,這些結構通常用於當一個進程沒有按照規範來操做文件時,當進程被殺掉後,系統任然能夠對這些不規範的操做進行管理。固然,後面還有不少內容也是如此,咱們就不一一敘說了,咱們只看建立一個進程的相關重點。linux
struct task_struct {
volatile long state; //說明了該進程是否能夠執行,仍是可中斷等信息
unsigned long flags; //進程號,在調用fork()時給出
int sigpending; //進程上是否有待處理的信號
mm_segment_t addr_limit; //進程地址空間,區份內核進程與普通進程在內存存放的位置不一樣
//0-0xBFFFFFFF for user-thead
//0-0xFFFFFFFF for kernel-thread
//調度標誌,表示該進程是否須要從新調度,若非0,則當從內核態返回到用戶態,會發生調度
volatile long need_resched;
int lock_depth; //鎖深度
long nice; //進程的基本時間片
//進程的調度策略,有三種,實時進程:SCHED_FIFO,SCHED_RR, 分時進程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //進程內存管理信息
int processor;
//若進程不在任何CPU上運行, cpus_runnable 的值是0,不然是1 這個值在運行隊列被鎖時更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向運行隊列的指針
unsigned long sleep_time; //進程的睡眠時間
//用於將系統中全部的進程連成一個雙向循環鏈表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages; //指向本地頁面
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt; //進程所運行的可執行文件的格式
int exit_code, exit_signal;
int pdeath_signal; //父進程終止是向子進程發送的信號
unsigned long personality;
int did_exec:1;
pid_t pid; //進程標識符,用來表明一個進程
pid_t pgrp; //進程組標識,表示進程所屬的進程組
pid_t tty_old_pgrp; //進程控制終端所在的組標識
pid_t session; //進程的會話標識
pid_t tgid;
int leader; //表示進程是否爲會話主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group; //線程鏈表
struct task_struct *pidhash_next; //用於將進程鏈入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit; //供wait4()使用
struct completion *vfork_done; //供vfork() 使用
unsigned long rt_priority; //實時優先級,用它計算實時進程調度時的weight值
……
};
3.進程建立分析
fork函數到底如何進行對應的內核處理過程sys_clone。session
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < 0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/* child process */
printf("This is Child Process!\n");
}
else
{
/* parent process */
printf("This is Parent Process!\n");
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
}
檢測題目的最後一個是什麼鬼 Linux中,fork()系統調用產生的子進程在系統調用處理過程當中從(:ret_from_fork)處開始,我要留着之後看一下,莫名其妙
Linux中,PCB task_struct中不包含哪一個信息()?
進程狀態
進程打開的文件
進程優先級信息
進程包含的線程列表信息
建立一個新進程在內核中的執行過程
fork、vfork和clone三個系統調用均可以建立一個新進程,並且都是經過調用do_fork來實現進程的建立;
Linux經過複製父進程來建立一個新進程,那麼這就給咱們理解這一個過程提供一個想象的框架:
複製一個PCB——task_struct數據結構
err = arch_dup_task_struct(tsk, orig);框架
要給新進程分配一個新的內核堆棧dom
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //這裏只是複製thread_info,而非複製內核堆棧
要修改複製過來的進程數據,好比pid、進程鏈表等等都要改改吧,見copy_process內部。
從用戶態的代碼看fork();函數返回了兩次,即在父子進程中各返回一次,父進程從系統調用中返回比較容易理解,子進程從系統調用中返回,那它在系統調用處理過程當中的哪裏開始執行的呢?這就涉及子進程的內核堆棧數據狀態和task_struct中thread記錄的sp和ip的一致性問題,這是在哪裏設定的?copy_thread in copy_process函數
*childregs = *current_pt_regs(); //複製內核堆棧
childregs->ax = 0; //爲何子進程的fork返回0,這裏就是緣由!
p->thread.sp = (unsigned long) childregs; //調度到子進程時的內核棧頂
p->thread.ip = (unsigned long) ret_from_fork; //調度到子進程時的第一條指令地址
按流程來講首先會進入do_fork,在do_fork裏,對一些狀況進行判斷。若是沒有什麼危險的狀況,則開始進入copy_process。
copy_process函數在進程建立的do_fork函數中調用,主要完成進程數據結構,各類資源的初始化。初始化方式能夠從新分配,也能夠共享父進程資源,主要根據傳入CLONE參數來肯定。ui
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
int node = tsk_fork_get_node(orig);
int err;
tsk = alloc_task_struct_node(node);
if (!tsk)
return NULL;
ti = alloc_thread_info_node(tsk, node);
if (!ti)
goto free_tsk;
err = arch_dup_task_struct(tsk, orig);
if (err)
goto free_ti;
tsk->stack = ti;
# ifdef CONFIG_SECCOMP
tsk->seccomp.filter = NULL;
# endif
setup_thread_stack(tsk, orig);
clear_user_return_notifier(tsk);
clear_tsk_need_resched(tsk);
set_task_stack_end_magic(tsk);
# ifdef CONFIG_CC_STACKPROTECTOR
tsk->stack_canary = get_random_int();
# endif
atomic_set(&tsk->usage, 2);
# ifdef CONFIG_BLK_DEV_IO_TRACE
tsk->btrace_seq = 0;
# endif
tsk->splice_pipe = NULL;
tsk->task_frag.page = NULL;
account_kernel_stack(ti, 1);
return tsk;
free_ti:
free_thread_info(ti);
free_tsk:
free_task_struct(tsk);
return NULL;
}
tsk = alloc_task_struct_node(node);爲task_struct開闢內存
ti = alloc_thread_info_node(tsk, node);ti指向thread_info的首地址,同時也是系統爲新進程分配的兩個連續頁面的首地址。
err = arch_dup_task_struct(tsk, orig);複製父進程的task_struct信息到新的task_struct裏, (dst = src;)
tsk->stack = ti;task的對應棧
setup_thread_stack(tsk, orig);初始化thread info結構
set_task_stack_end_magic(tsk);棧結束的地址設置數據爲棧結束標示(for overflow detection)atom
int copy_thread(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p)
{
...
*childregs = *current_pt_regs();
childregs->ax = 0;
if (sp)
childregs->sp = sp;
p->thread.ip = (unsigned long) ret_from_fork;
...
}
子進程執行ret_from_forkspa
ENTRY(ret_from_fork)
CFI_STARTPROC
pushl_cfi %eax
call schedule_tail
GET_THREAD_INFO(%ebp)
popl_cfi %eax
pushl_cfi $0x0202 # Reset kernel eflags
popfl_cfi
jmp syscall_exit
CFI_ENDPROC
END(ret_from_fork)
===================== 在ret_from_fork以前,也就是在copy_thread()函數中*childregs = *current_pt_regs();該句將父進程的regs參數賦值到子進程的內核堆棧, *childregs的類型爲pt_regs,裏面存放了SAVE ALL中壓入棧的參數 故在以後的RESTORE ALL中能順利執行下去. 7. 未完待續。。 8. 望手下留情