Linux進(jìn)程詳解,從這五點(diǎn)入手!
進(jìn)程
程序是指儲(chǔ)存在外部存儲(chǔ)(如硬盤(pán))的一個(gè)可執(zhí)行文件, 而進(jìn)程是指處于執(zhí)行期間的程序, 進(jìn)程包括 ?和 , 除了代碼段和數(shù)據(jù)段外, 進(jìn)程一般還包含打開(kāi)的文件, 要處理的信號(hào)和CPU上下文等等.代碼段(text section)數(shù)據(jù)段(data section)
進(jìn)程描述符
Linux進(jìn)程使用 來(lái)描述(include/linux/sched.h), 如下:struct task_struct
struct task_struct {
? ?/*
? ? * offsets of these are hardcoded elsewhere - touch with care
? ? */
? ? volatile long state; ? ?/* -1 unrunnable, 0 runnable, >0 stopped */
? ? unsigned long flags; ? ?/* per process flags, defined below */
? ? int sigpending;
? ? mm_segment_t addr_limit; ? ?/* thread address space:
? ? ? ? ? ? ? ? ? ? ? ?0-0xBFFFFFFF for user-thead
? ? ? ? ? ? ? ? ? ? ? ?0-0xFFFFFFFF for kernel-thread
? ? ? ? ? ? ? ? ? ? */
? ? struct exec_domain *exec_domain;
? ? volatile long need_resched;
? ? unsigned long ptrace;
? ? int lock_depth; ? ? /* Lock depth */
/*
* offset 32 begins here on 32-bit platforms. We keep
* all fields in a single cacheline that are needed for
* the goodness() loop in schedule().
*/
? ? long counter;
? ? long nice;
? ? unsigned long policy;
? ? struct mm_struct *mm;
? ? int processor;
? ? ...
}
Linux把所有的進(jìn)程使用雙向鏈表連接起來(lái), 如下圖(來(lái)源<Linux設(shè)計(jì)與實(shí)現(xiàn)>):

Linux內(nèi)核為了加快獲取當(dāng)前進(jìn)程的的task_struct結(jié)構(gòu), 使用了一個(gè)技巧, 就是把task_struct放置在內(nèi)核棧的棧底, 這樣就可以通過(guò) 快速獲取到當(dāng)前運(yùn)行進(jìn)程的task_struct結(jié)構(gòu).如下圖:esp寄存器

取當(dāng)前運(yùn)行進(jìn)程的task_struct代碼如下:
static inline struct task_struct * get_current(void)
{
? ? struct task_struct *current;
? ? __asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
? ? return current;
}
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺(jué)得比較好的學(xué)習(xí)書(shū)籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!前100名進(jìn)群領(lǐng)取,額外贈(zèng)送一份價(jià)值699的內(nèi)核資料包(含視頻教程、電子書(shū)、實(shí)戰(zhàn)項(xiàng)目及代碼)?


進(jìn)程狀態(tài)
進(jìn)程描述符的state字段用于保存進(jìn)程的當(dāng)前狀態(tài), 進(jìn)程的狀態(tài)有以下幾種:
TASK_RUNNING (運(yùn)行)– 進(jìn)程處于可執(zhí)行狀態(tài), 在這個(gè)狀態(tài)下的進(jìn)程要么正在被CPU執(zhí)行, 要么在等待執(zhí)行(CPU被其他進(jìn)程占用的情況下).
TASK_INTERRUPTIBLE (可中斷等待)– 進(jìn)程處于等待狀態(tài), 其在等待某些條件成立或者接收到某些信號(hào), 進(jìn)程會(huì)被喚醒變?yōu)檫\(yùn)行狀態(tài).
TASK_UNINTERRUPTIBLE (不可中斷等待)– 進(jìn)程處于等待狀態(tài), 其在等待某些條件成立, 進(jìn)程會(huì)被喚醒變?yōu)檫\(yùn)行狀態(tài), 但不能被信號(hào)喚醒.
TASK_TRACED (被追蹤)– 進(jìn)程處于被追蹤狀態(tài), 例如通過(guò)ptrace命令對(duì)進(jìn)程進(jìn)行調(diào)試.
TASK_STOPPED (停止)– 進(jìn)程處于停止?fàn)顟B(tài), 進(jìn)程不能被執(zhí)行.一般接收到SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU信號(hào)進(jìn)程會(huì)變成TASK_STOPPED狀態(tài).
個(gè)鐘狀態(tài)間的轉(zhuǎn)換如下圖:

進(jìn)程的創(chuàng)建
在Linux系統(tǒng)中,進(jìn)程的創(chuàng)建使用fork()系統(tǒng)調(diào)用,fork()調(diào)用會(huì)創(chuàng)建一個(gè)與父進(jìn)程一樣的子進(jìn)程,唯一不同就是fork()的返回值,父進(jìn)程返回的是子進(jìn)程的進(jìn)程ID,而子進(jìn)程返回的是0。
Linux創(chuàng)建子進(jìn)程時(shí)使用了,也就是創(chuàng)建子進(jìn)程時(shí)使用的是父進(jìn)程的內(nèi)存空間,當(dāng)子進(jìn)程或者父進(jìn)程修改數(shù)據(jù)時(shí)才會(huì)復(fù)制相應(yīng)的內(nèi)存頁(yè)。寫(xiě)時(shí)復(fù)制(Copy On Write)
當(dāng)調(diào)用fork()系統(tǒng)調(diào)用時(shí)會(huì)陷入內(nèi)核空間并且調(diào)用sys_fork()函數(shù),sys_fork()函數(shù)會(huì)調(diào)用do_fork()函數(shù),代碼如下(arch/i386/kernel/process.c):
asmlinkage int sys_fork(struct pt_regs regs)
{
? ? return do_fork(SIGCHLD, regs.esp, ?s, 0);
}
do_fork()主要的工作是申請(qǐng)一個(gè)進(jìn)程描述符, 然后初始化進(jìn)程描述符的各個(gè)字段, 包括調(diào)用 copy_files() 函數(shù)復(fù)制打開(kāi)的文件, 調(diào)用 copy_sighand() 函數(shù)復(fù)制信號(hào)處理函數(shù), 調(diào)用 copy_mm() 函數(shù)復(fù)制進(jìn)程虛擬內(nèi)存空間, 調(diào)用 copy_namespace() 函數(shù)復(fù)制命名空間.代碼如下:
int do_fork(unsigned long clone_flags, unsigned long stack_start,
? ? ? ?struct pt_regs *regs, unsigned long stack_size)
{
? ? ...
? ? p = alloc_task_struct(); // 申請(qǐng)進(jìn)程描述符
? ? ...
? ? if (copy_files(clone_flags, p))
? ? ? ? ?goto bad_fork_cleanup;
? ? if (copy_fs(clone_flags, p))
? ? ? ? ?goto bad_fork_cleanup_files;
? ? if (copy_sighand(clone_flags, p))
? ? ? ? ?goto bad_fork_cleanup_fs;
? ? if (copy_mm(clone_flags, p))
? ? ? ? ?goto bad_fork_cleanup_sighand;
? ? if (copy_namespace(clone_flags, p))
? ? ? ? ?goto bad_fork_cleanup_mm;
? ? retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);
? ? ...
? ? wake_up_process(p);
? ? ...
}
值得注意的是do_fork() 還調(diào)用了 copy_thread() 這個(gè)函數(shù), copy_thread()這個(gè)函數(shù)主要用于設(shè)置進(jìn)程的CPU執(zhí)行上下文 結(jié)構(gòu).代碼如下:struct thread_struct
int copy_thread(int nr, unsigned long clone_flags, unsigned long esp,
? ?unsigned long unused,
? ?struct task_struct * p, struct pt_regs * regs)
{
? ? struct pt_regs * childregs;
? ? // 指向棧頂(見(jiàn)圖2)
? ? childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p)) - 1;
? ? struct_cpy(childregs, regs); ?// 復(fù)制父進(jìn)程的棧信息
? ? childregs->eax = 0; ? // 這個(gè)是子進(jìn)程調(diào)用fork()之后的返回值, 也就是0
? ? childregs->esp = esp; // 設(shè)置新的棧空間
? ? p->thread.esp = (unsigned long) childregs; ? ? ?// 子進(jìn)程當(dāng)前的棧地址, 調(diào)用switch_to()的時(shí)候esp設(shè)置為這個(gè)地址
? ? p->thread.esp0 = (unsigned long) (childregs+1); // 子進(jìn)程內(nèi)核空間棧地址
? ? p->thread.eip = (unsigned long) ret_from_fork; ?// 子進(jìn)程將要執(zhí)行的代碼地址
? ? savesegment(fs,p->thread.fs);
? ? savesegment(gs,p->thread.gs);
? ? unlazy_fpu(current);
? ? struct_cpy(&p->thread.i387, ¤t->thread.i387);
? ? return 0;
}
do_fork() 函數(shù)最后調(diào)用 wake_up_process() 函數(shù)喚醒子進(jìn)程, 讓子進(jìn)程進(jìn)入運(yùn)行狀態(tài).
內(nèi)核線程
Linux內(nèi)核有很多任務(wù)需要去做, 例如定時(shí)把緩沖中的數(shù)據(jù)刷到硬盤(pán), 當(dāng)內(nèi)存不足的時(shí)候進(jìn)行內(nèi)存的回收等, 這些工作都需要通過(guò)內(nèi)核線程來(lái)完成.內(nèi)核線程與普通進(jìn)程的主要區(qū)別就是: 內(nèi)核線程沒(méi)有自己的 , 每次內(nèi)核線程執(zhí)行的時(shí)候都是借助當(dāng)前運(yùn)行進(jìn)程的虛擬內(nèi)存空間結(jié)構(gòu)來(lái)運(yùn)行, 因?yàn)閮?nèi)核線程只會(huì)運(yùn)行在內(nèi)核態(tài), 而每個(gè)進(jìn)程的內(nèi)核態(tài)空間都是一樣的, 所以借助其他進(jìn)程的虛擬內(nèi)存空間結(jié)構(gòu)來(lái)運(yùn)行是完成可行的.虛擬空間結(jié)構(gòu)(struct mm)
內(nèi)核線程使用 kernel_thread() 函數(shù)來(lái)創(chuàng)建, 代碼如下:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
? ?long retval, d0;
? ?__asm__ __volatile__(
? ? ? ?"movl %%esp,%%esi\n\t"
? ? ? ?"int $0x80\n\t" ? ? ? ?/* Linux/i386 system call */
? ? ? ?"cmpl %%esp,%%esi\n\t" ?/* child or parent? */
? ? ? ?"je 1f\n\t" ? ? /* parent - jump */
? ? ? ?/* Load the argument into eax, and push it. ?That way, it does
? ? ? ? * not matter whether the called function is compiled with
? ? ? ? * -mregparm or not. ?*/
? ? ? ?"movl %4,%%eax\n\t"
? ? ? ?"pushl %%eax\n\t" ? ? ?
? ? ? ?"call *%5\n\t" ? ? ?/* call fn */
? ? ? ?"movl %3,%0\n\t" ? ?/* exit */
? ? ? ?"int $0x80\n"
? ? ? ?"1:\t"
? ?:"=&a" (retval), "=&S" (d0)
? ?:"0" (__NR_clone), "i" (__NR_exit),
? ?"r" (arg), "r" (fn),
? ?"b" (flags | CLONE_VM)
? ?: "memory");
? ?return retval;
}
因?yàn)檫@個(gè)函數(shù)式使用嵌入?yún)R編來(lái)實(shí)現(xiàn)的, 所以有點(diǎn)難懂, 不過(guò)主要過(guò)程就是通過(guò)調(diào)用 ?來(lái)創(chuàng)建一個(gè)新的進(jìn)程, 而創(chuàng)建進(jìn)程是通過(guò)傳入 ?標(biāo)志來(lái)指定進(jìn)程借用其他進(jìn)程的虛擬內(nèi)存空間結(jié)構(gòu)_clone()函數(shù)CLONE_VM
