linux內核設計與實現---進程管理

進程管理

  • 1 進程描述符及任務結構
    • 分配進程描述符
    • 進程描述符的存放
    • 進程狀態
    • 設置當前進程狀態
    • 進程上下文
    • 進程家族樹
  • 2 進程創建
    • 寫時拷貝
    • fork()
    • vfork()
  • 3 線程在Linux中的實現
      • 內核線程
  • 4 進程終結
    • 刪除進程描述符
    • 孤兒進程造成的進退微谷
  • 5 小結

進程的另一個名字叫做任務(task)。Linux內核通常把進程也叫做任務。下面我們會交替使用任務和進程兩個術語。

1 進程描述符及任務結構

內核把進程存放在叫作任務隊列(task list)的雙向循環鏈表中。鏈表中的每一項都是類型為task_struct、稱為進程描述符的結構,該結構定義在include/linux/sched.h文件中。進程描述符包含一個具體進程的所有信息。

struct task_struct {volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */struct thread_info *thread_info;atomic_t usage;unsigned long flags;	/* per process flags, defined below */unsigned long ptrace;int lock_depth;		/* Lock depth */int prio, static_prio;struct list_head run_list;prio_array_t *array;unsigned long sleep_avg;long interactive_credit;unsigned long long timestamp, last_ran;int activated;unsigned long policy;cpumask_t cpus_allowed;unsigned int time_slice, first_time_slice;#ifdef CONFIG_SCHEDSTATSstruct sched_info sched_info;
#endifstruct list_head tasks;/** ptrace_list/ptrace_children forms the list of my children* that were stolen by a ptracer.*/struct list_head ptrace_children;struct list_head ptrace_list;struct mm_struct *mm, *active_mm;/* task state */struct linux_binfmt *binfmt;long exit_state;int exit_code, exit_signal;int pdeath_signal;  /*  The signal sent when the parent dies  *//* ??? */unsigned long personality;unsigned did_exec:1;pid_t pid;pid_t tgid;/* * pointers to (original) parent process, youngest child, younger sibling,* older sibling, respectively.  (p->father can be replaced with * p->parent->pid)*/struct task_struct *real_parent; /* real parent process (when being debugged) */struct task_struct *parent;	/* parent process *//** children/sibling forms the list of my children plus the* tasks I'm ptracing.*/struct list_head children;	/* list of my children */struct list_head sibling;	/* linkage in my parent's children list */struct task_struct *group_leader;	/* threadgroup leader *//* PID/PID hash table linkage. */struct pid pids[PIDTYPE_MAX];wait_queue_head_t wait_chldexit;	/* for wait4() */struct completion *vfork_done;		/* for vfork() */int __user *set_child_tid;		/* CLONE_CHILD_SETTID */int __user *clear_child_tid;		/* CLONE_CHILD_CLEARTID */unsigned long rt_priority;unsigned long it_real_value, it_prof_value, it_virt_value;unsigned long it_real_incr, it_prof_incr, it_virt_incr;struct timer_list real_timer;unsigned long utime, stime;unsigned long nvcsw, nivcsw; /* context switch counts */struct timespec start_time;
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */unsigned long min_flt, maj_flt;
/* process credentials */uid_t uid,euid,suid,fsuid;gid_t gid,egid,sgid,fsgid;struct group_info *group_info;kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;unsigned keep_capabilities:1;struct user_struct *user;
#ifdef CONFIG_KEYSstruct key *session_keyring;	/* keyring inherited over fork */struct key *process_keyring;	/* keyring private to this process (CLONE_THREAD) */struct key *thread_keyring;	/* keyring private to this thread */
#endifunsigned short used_math;char comm[16];
/* file system info */int link_count, total_link_count;
/* ipc stuff */struct sysv_sem sysvsem;
/* CPU-specific state of this task */struct thread_struct thread;
/* filesystem information */struct fs_struct *fs;
/* open file information */struct files_struct *files;
/* namespace */struct namespace *namespace;
/* signal handlers */struct signal_struct *signal;struct sighand_struct *sighand;sigset_t blocked, real_blocked;struct sigpending pending;unsigned long sas_ss_sp;size_t sas_ss_size;int (*notifier)(void *priv);void *notifier_data;sigset_t *notifier_mask;void *security;struct audit_context *audit_context;/* Thread group tracking */u32 parent_exec_id;u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */spinlock_t alloc_lock;
/* Protection of proc_dentry: nesting proc_lock, dcache_lock, write_lock_irq(&tasklist_lock); */spinlock_t proc_lock;
/* context-switch lock */spinlock_t switch_lock;/* journalling filesystem info */void *journal_info;/* VM state */struct reclaim_state *reclaim_state;struct dentry *proc_dentry;struct backing_dev_info *backing_dev_info;struct io_context *io_context;unsigned long ptrace_message;siginfo_t *last_siginfo; /* For ptrace use.  */
/** current io wait handle: wait queue entry to use for io waits* If this thread is processing aio, this points at the waitqueue* inside the currently handled kiocb. It may be NULL (i.e. default* to a stack based synchronous wait) if its doing sync IO.*/wait_queue_t *io_wait;
#ifdef CONFIG_NUMAstruct mempolicy *mempolicy;short il_next;		/* could be shared with used_math */
#endif
};

分配進程描述符

Linux通過slab分配器分配task_struct結構,這樣能達到對象復用和緩存著色的目的(通過預先分配和重復使用task_struct,可以避免動態分配和釋放所帶來的資源消耗)。在2.6以前的內核中,各個進程的task_struct存放在它們內核棧的尾端。這樣做是為了讓那些x86這樣寄存器較少的硬件體系結構只要通過棧指針就能計算出它的位置。由于現在用slab分配器動態生成task_struct,所以只需在棧底(對于向下增長的棧來說)或棧頂(對于向上增長的的棧來說)創建一個新的結構struct thread_info,thread_info有一個指向進程描述符的指針。
在這里插入圖片描述

在x86_64上,thread_info結構在文件include/asm-x86_64/thread_info.h中

struct thread_info {struct task_struct	*task;		/* main task structure */struct exec_domain	*exec_domain;	/* execution domain */__u32			flags;		/* low level flags */__u32			status;		/* thread synchronous flags */__u32			cpu;		/* current CPU */int 			preempt_count;mm_segment_t		addr_limit;	struct restart_block    restart_block;
};

每個任務的thread_info結構在它的內核棧的尾部分配。結構中task域存放的是指向該任務實際task_struct的指針。每個進程都有一個thread_info結構,指向自己的task_struct進程描述符。

進程描述符的存放

內核通過一個唯一的進程標識值或PID來標識每個進程。PID是pid_t類型,是一個int類型。為了與老版本的Unix和Linux兼容,PID的最大值默認設置為32768,內核把每個進程的PID存放在它們各自的進程描述符中。
這個最大值很重要,因為它實際上就是系統中允許同時存在的進程的最大數目。如果確實需要的話,由系統管理員通過修改/proc/sys/kernel/pid_max來提高上線。
在內核中,訪問任務通常需要獲得指向其task_struct指針。可以通過current宏查找到當前正在運行進程的進程描述符。有的硬件體系結構可以拿出一個專門寄存器來存放當前進程task_struct的地址。
在x86系統上,current宏定義在include/asm-m32r/current.h文件中。current把棧指針的后13個有效數字位屏蔽掉,用來計算出thread_info的偏移,因為thread_info結構存放在它的內核棧的尾端

static __inline__ struct task_struct *get_current(void)
{return current_thread_info()->task;
}#define current	(get_current())static inline struct thread_info *current_thread_info(void)
{struct thread_info *ti;__asm__ __volatile__ ("ldi	%0, #0xffffe000;	\n\t""and	%0, sp;			\n\t": "=r" (ti));return ti;
}

這里內核棧的大小是8KB,兩頁,13位可以標識8kb內存地址,屏蔽13位剛剛好指向棧的尾端。

進程狀態

進程描述符中的state域描述了進程的當前狀態

volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */

系統中的每個進程都必然處于五種進程狀態中的一種。該域的值也必然為下列五種狀態標志之一:

  • TASK_RUNNING(運行):進程是可執行的;它或者正在執行,或者在運行隊列中等待執行
  • TASK_INTERRUPTIBLE(可中斷):進程正在睡眠(也就是被阻塞),等待某些條件的達成。一旦這些條件達成,內核就會把進程狀態設置為運行。
  • TASK_UNINTERRUPTIBLE(不可中斷):除了不會因為接受到信號而被喚醒從而投入運行外,這個狀態與可打斷狀態相同。
  • TASK_ZOMBIE(僵死):該進程已經結束了,但是符父進程還沒有調用wait4()系統調用,釋放相關資源。
  • TASK_STOPPED(停止):進程停止運行。

請添加圖片描述

設置當前進程狀態

內核經常需要調整某個進程的狀態。這時最好使用set_task_state函數:

set_task_state(task,state)//將任務task的狀態設置為state
#define set_task_state(tsk, state_value)		\set_mb((tsk)->state, (state_value))

該函數將指定的進程設置為指定的狀態。位置在/include/linux/sched.h文件中。
方法set_current_state(state)和set_task_state(current, state)含義是相同的。

進程上下文

一般程序在用戶空間執行。當一個程序執行了系統調用或者觸發了某個異常,它就陷入了內核空間。此時,我們稱內核"代表進程執行"并處于進程上下文中。在此上下文中的current宏是有效的。除非在此間隙有更高優先級的進程需要執行并由調度器作出了相應調整,否則在內核退出的時候,程序恢復在用戶空間繼續執行。

進程家族樹

Unix系統的進程之間存在明顯的繼承關系,在Linux系統中也是如此。所有的進程都是PID為1的init進程的后代。系統中的每個進程必有一個父進程。相應的,每個進程也可以擁有零個或多個子進程。進程間的關系存放在進程描述符中。每個task_struct都包含一個指向其父進程進程描述符的parent指向,還包含一個稱為children的子進程鏈表。所以對于當前進程,可以通過下面的代碼獲得其父進程的進程描述符:

struct task_struct *my_parent = current->parent;	//current是指向當前進程的進程描述符指針

同樣,也可以按以下方式依次訪問子進程

struct task_struct *task;
struct list_head *list;
list_for_each(list,&current->children){task = list_entry(list,struct task_struct,sibling);/* task現在指向當前的某個子進程 */
}

2 進程創建

Unix的進程創建很特別。許多其他的操作系統都提供了產生進程的機制,首先在新的地址空間里創建進程,讀入可執行文件,最后開始執行。Unix把上述步驟分解到兩個單獨的函數中去執行:fork()和exec()。首先,fork()通過拷貝當前進程創建一個子進程。然后exec()函數負責讀取可執行文件并將其載入地址空間開始執行。

寫時拷貝

Linux的fork()使用寫時拷貝(copy-on-write)頁實現。寫時拷貝是一種可以推遲甚至免除拷貝數據的技術。當調用fork時,內核此時并不復制整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資源的復制只有在需要寫入的時候才進行,在此之前,只是以只讀方式共享。

fork()

Linux通過clone()系統調用實現 fork()。這個調用通過一系列的參數標志來指明父子進程需要共享的資源。fork()、vfork()和__clone()庫函數都根據各自需要的參數標志去調用clone()。然后由clone()去調用do_fork()。
do_fork()完成了創建中的大部分工作,它的定義在kernel/fork.c文件中。該函數調用copy_process函數,然后讓進程開始運行。copy_process函數完成的工作很有意思:

static task_t *copy_process(unsigned long clone_flags,unsigned long stack_start,struct pt_regs *regs,unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr,int pid)
{int retval;struct task_struct *p = NULL;if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))return ERR_PTR(-EINVAL);/** Thread groups must share signals as well, and detached threads* can only be started up within the thread group.*/if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))return ERR_PTR(-EINVAL);/** Shared signal handlers imply shared VM. By way of the above,* thread groups also imply shared VM. Blocking this case allows* for various simplifications in other code.*/if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))return ERR_PTR(-EINVAL);retval = security_task_create(clone_flags);if (retval)goto fork_out;retval = -ENOMEM;p = dup_task_struct(current);if (!p)goto fork_out;retval = -EAGAIN;if (atomic_read(&p->user->processes) >=p->signal->rlim[RLIMIT_NPROC].rlim_cur) {if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&p->user != &root_user)goto bad_fork_free;}atomic_inc(&p->user->__count);atomic_inc(&p->user->processes);get_group_info(p->group_info);/** If multiple threads are within copy_process(), then this check* triggers too late. This doesn't hurt, the check is only there* to stop root fork bombs.*/if (nr_threads >= max_threads)goto bad_fork_cleanup_count;if (!try_module_get(p->thread_info->exec_domain->module))goto bad_fork_cleanup_count;if (p->binfmt && !try_module_get(p->binfmt->module))goto bad_fork_cleanup_put_domain;p->did_exec = 0;copy_flags(clone_flags, p);p->pid = pid;retval = -EFAULT;if (clone_flags & CLONE_PARENT_SETTID)if (put_user(p->pid, parent_tidptr))goto bad_fork_cleanup;p->proc_dentry = NULL;INIT_LIST_HEAD(&p->children);INIT_LIST_HEAD(&p->sibling);init_waitqueue_head(&p->wait_chldexit);p->vfork_done = NULL;spin_lock_init(&p->alloc_lock);spin_lock_init(&p->proc_lock);clear_tsk_thread_flag(p, TIF_SIGPENDING);init_sigpending(&p->pending);p->it_real_value = p->it_virt_value = p->it_prof_value = 0;p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;init_timer(&p->real_timer);p->real_timer.data = (unsigned long) p;p->utime = p->stime = 0;p->lock_depth = -1;		/* -1 = no lock */do_posix_clock_monotonic_gettime(&p->start_time);p->security = NULL;p->io_context = NULL;p->io_wait = NULL;p->audit_context = NULL;
#ifdef CONFIG_NUMAp->mempolicy = mpol_copy(p->mempolicy);if (IS_ERR(p->mempolicy)) {retval = PTR_ERR(p->mempolicy);p->mempolicy = NULL;goto bad_fork_cleanup;}
#endifp->tgid = p->pid;if (clone_flags & CLONE_THREAD)p->tgid = current->tgid;if ((retval = security_task_alloc(p)))goto bad_fork_cleanup_policy;if ((retval = audit_alloc(p)))goto bad_fork_cleanup_security;/* copy all the process information */if ((retval = copy_semundo(clone_flags, p)))goto bad_fork_cleanup_audit;if ((retval = copy_files(clone_flags, p)))goto bad_fork_cleanup_semundo;if ((retval = copy_fs(clone_flags, p)))goto bad_fork_cleanup_files;if ((retval = copy_sighand(clone_flags, p)))goto bad_fork_cleanup_fs;if ((retval = copy_signal(clone_flags, p)))goto bad_fork_cleanup_sighand;if ((retval = copy_mm(clone_flags, p)))goto bad_fork_cleanup_signal;if ((retval = copy_keys(clone_flags, p)))goto bad_fork_cleanup_mm;if ((retval = copy_namespace(clone_flags, p)))goto bad_fork_cleanup_keys;retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);if (retval)goto bad_fork_cleanup_namespace;p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;/** Clear TID on mm_release()?*/p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;/** Syscall tracing should be turned off in the child regardless* of CLONE_PTRACE.*/clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);/* Our parent execution domain becomes current domainThese must match for thread signalling to apply */p->parent_exec_id = p->self_exec_id;/* ok, now we should be set up.. */p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);p->pdeath_signal = 0;p->exit_state = 0;/* Perform scheduler related setup */sched_fork(p);/** Ok, make it visible to the rest of the system.* We dont wake it up yet.*/p->group_leader = p;INIT_LIST_HEAD(&p->ptrace_children);INIT_LIST_HEAD(&p->ptrace_list);/* Need tasklist lock for parent etc handling! */write_lock_irq(&tasklist_lock);/** The task hasn't been attached yet, so cpus_allowed mask cannot* have changed. The cpus_allowed mask of the parent may have* changed after it was copied first time, and it may then move to* another CPU - so we re-copy it here and set the child's CPU to* the parent's CPU. This avoids alot of nasty races.*/p->cpus_allowed = current->cpus_allowed;set_task_cpu(p, smp_processor_id());/** Check for pending SIGKILL! The new thread should not be allowed* to slip out of an OOM kill. (or normal SIGKILL.)*/if (sigismember(&current->pending.signal, SIGKILL)) {write_unlock_irq(&tasklist_lock);retval = -EINTR;goto bad_fork_cleanup_namespace;}/* CLONE_PARENT re-uses the old parent */if (clone_flags & (CLONE_PARENT|CLONE_THREAD))p->real_parent = current->real_parent;elsep->real_parent = current;p->parent = p->real_parent;if (clone_flags & CLONE_THREAD) {spin_lock(&current->sighand->siglock);/** Important: if an exit-all has been started then* do not create this new thread - the whole thread* group is supposed to exit anyway.*/if (current->signal->group_exit) {spin_unlock(&current->sighand->siglock);write_unlock_irq(&tasklist_lock);retval = -EAGAIN;goto bad_fork_cleanup_namespace;}p->group_leader = current->group_leader;if (current->signal->group_stop_count > 0) {/** There is an all-stop in progress for the group.* We ourselves will stop as soon as we check signals.* Make the new thread part of that group stop too.*/current->signal->group_stop_count++;set_tsk_thread_flag(p, TIF_SIGPENDING);}spin_unlock(&current->sighand->siglock);}SET_LINKS(p);if (unlikely(p->ptrace & PT_PTRACED))__ptrace_link(p, current->parent);attach_pid(p, PIDTYPE_PID, p->pid);attach_pid(p, PIDTYPE_TGID, p->tgid);if (thread_group_leader(p)) {attach_pid(p, PIDTYPE_PGID, process_group(p));attach_pid(p, PIDTYPE_SID, p->signal->session);if (p->pid)__get_cpu_var(process_counts)++;}nr_threads++;write_unlock_irq(&tasklist_lock);retval = 0;fork_out:if (retval)return ERR_PTR(retval);return p;bad_fork_cleanup_namespace:exit_namespace(p);
bad_fork_cleanup_keys:exit_keys(p);
bad_fork_cleanup_mm:if (p->mm)mmput(p->mm);
bad_fork_cleanup_signal:exit_signal(p);
bad_fork_cleanup_sighand:exit_sighand(p);
bad_fork_cleanup_fs:exit_fs(p); /* blocking */
bad_fork_cleanup_files:exit_files(p); /* blocking */
bad_fork_cleanup_semundo:exit_sem(p);
bad_fork_cleanup_audit:audit_free(p);
bad_fork_cleanup_security:security_task_free(p);
bad_fork_cleanup_policy:
#ifdef CONFIG_NUMAmpol_free(p->mempolicy);
#endif
bad_fork_cleanup:if (p->binfmt)module_put(p->binfmt->module);
bad_fork_cleanup_put_domain:module_put(p->thread_info->exec_domain->module);
bad_fork_cleanup_count:put_group_info(p->group_info);atomic_dec(&p->user->processes);free_uid(p->user);
bad_fork_free:free_task(p);goto fork_out;
}
  • 調用dup_task_struct()為新進程創建一個內核棧、thread_info結構和task_struct,這些值與當前進程的值相同。此時,子進程和父進程的描述符是完全相同的。
  • 檢查新創建的這個子進程后,當前用戶所擁有的進程數目沒有超過給他分配的資源的限制
  • 現在,子進程著手使自己與父進程區別開來。進程描述符內的許多成員都要被清0或設為初始值。
  • 接下來,子進程的狀態被設置為TASK_UNINTERRUPTIBLE以保證它不會投入運行
  • copy_process()調用copy_flags()以更新task_struct的flags成員。
  • 調用get_pid()為新進程獲取一個有效的PID
  • 根據傳遞給clone的參數標志,copy_process拷貝或共享打開的文件、文件系統消息等。
  • 讓父進程和子進程平分剩余的時間片
  • 最后,copy_process做掃尾工作并放回一個指向子進程的指針。
    再回到do_fork()函數,如果copy_process()成功返回,新創建的子進程被喚醒并讓其投入運行。

vfork()

vfork系統調用和fork()的功能相同,除了不拷貝父進程的頁表項。子進程作為父進程的一個單獨的線程在它的地址空間運行,父進程被阻塞,直到子進程退出或執行exec()。
vfork系統調用的實現是通過向clone系統調用傳遞一個特殊標志進行的。

  • 在調用copy_process()時,task_struct的vfork_done成員被設置為NULL
  • 在執行do_fork()時,如果給定特別標志,則vfork_done會指向一個特殊地址
  • 子進程開始執行后,父進程不是立馬恢復執行,而是一直等待,直到子進程通過vfork_done指針向它發送信號
  • 在調用mm_release()時,該函數用于進程退出內存地址空間,并且檢查vfork_done是否為空,如果不為空,則會向父進程發送信號。
  • 回到do_fork(),父進程醒來并返回。

3 線程在Linux中的實現

Linux實現線程的機制非常獨特。從內核角度來說,它并沒有線程這個概念。Linux把所有的線程都當做進程來實現。內核并沒有準備特別的調度算法或是定義特別的數據結構來表示線程。相反,線程僅僅被視為一個與其他進程共享某些資源的**進程。**每個進程都有唯一自己的task_struct。所以在內核中,它看起來像一個普通的線程。
線程的創建和普通進程的創建類似,只不過在調用clone的時候需要傳遞一些參數標志來指明需要的共享資源:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND,0)

上面的代碼產生的結構和調用fork差不多,只是父子進程共享地址空間、文件系統資源、文件描述符和信號處理程序。
一個普通的fork實現是:

clone(CLONE_VFORK | CLONE_VM  | CLONE_SIGHAND,0)

傳遞給clone的參數標志決定了新創建進程的行為方式和父子進程之間共享的資源種類。這些參數標志定義在include/linux/sched.h文件中。

/** cloning flags:*/
#define CSIGNAL		0x000000ff	/* signal mask to be sent at exit */
#define CLONE_VM	0x00000100	/* set if VM shared between processes */
#define CLONE_FS	0x00000200	/* set if fs info shared between processes */
#define CLONE_FILES	0x00000400	/* set if open files shared between processes */
#define CLONE_SIGHAND	0x00000800	/* set if signal handlers and blocked signals shared */
#define CLONE_PTRACE	0x00002000	/* set if we want to let tracing continue on the child too */
#define CLONE_VFORK	0x00004000	/* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT	0x00008000	/* set if we want to have the same parent as the cloner */
#define CLONE_THREAD	0x00010000	/* Same thread group? */
#define CLONE_NEWNS	0x00020000	/* New namespace group? */
#define CLONE_SYSVSEM	0x00040000	/* share system V SEM_UNDO semantics */
#define CLONE_SETTLS	0x00080000	/* create a new TLS for the child */
#define CLONE_PARENT_SETTID	0x00100000	/* set the TID in the parent */
#define CLONE_CHILD_CLEARTID	0x00200000	/* clear the TID in the child */
#define CLONE_DETACHED		0x00400000	/* Unused, ignored */
#define CLONE_UNTRACED		0x00800000	/* set if the tracing process can't force CLONE_PTRACE on this clone */
#define CLONE_CHILD_SETTID	0x01000000	/* set the TID in the child */
#define CLONE_STOPPED		0x02000000	/* Start in stopped state */

內核線程

內核線程是獨立運行在內核空間的標準進程,內核線程和普通的進程間的區別在于內核線程沒有獨立的地址空間(mm指針被設置為NULL)。它們只在內核空間運行,從來不切換到用戶空間去。內核進程和普通進程一樣,可以被調度,也可以被搶占。
內核線程只能由其他內核線程創建,在現有內核線程創建一個新的內核線程的方法如下:

int kernel_thread(int (*fn)(void*),void *arg,unsigned long flags);

新的任務也是通過向普通的clone()系統調用傳遞特定的flags參數而創建的。在上面的函數返回時,父線程退出,并返回一個指向子線程task_struct的指針。

4 進程終結

當一個進程終結時,內核必須釋放它所占有的資源并把這一消息傳給父進程。不論進程是怎樣終結的,該任務大部分都要靠do_exit()來完成,do_exit()定義在kernel/exit.c文件中

fastcall NORET_TYPE void do_exit(long code)
{struct task_struct *tsk = current;int group_dead;profile_task_exit(tsk);if (unlikely(in_interrupt()))panic("Aiee, killing interrupt handler!");if (unlikely(!tsk->pid))panic("Attempted to kill the idle task!");if (unlikely(tsk->pid == 1))panic("Attempted to kill init!");if (tsk->io_context)exit_io_context();tsk->flags |= PF_EXITING;del_timer_sync(&tsk->real_timer);if (unlikely(in_atomic()))printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n",current->comm, current->pid,preempt_count());if (unlikely(current->ptrace & PT_TRACE_EXIT)) {current->ptrace_message = code;ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);}group_dead = atomic_dec_and_test(&tsk->signal->live);if (group_dead)acct_process(code);__exit_mm(tsk);exit_sem(tsk);__exit_files(tsk);__exit_fs(tsk);exit_namespace(tsk);exit_thread();exit_keys(tsk);if (group_dead && tsk->signal->leader)disassociate_ctty(1);module_put(tsk->thread_info->exec_domain->module);if (tsk->binfmt)module_put(tsk->binfmt->module);tsk->exit_code = code;exit_notify(tsk);
#ifdef CONFIG_NUMAmpol_free(tsk->mempolicy);tsk->mempolicy = NULL;
#endifBUG_ON(!(current->flags & PF_DEAD));schedule();BUG();/* Avoid "noreturn function does return".  */for (;;) ;
}
  • 首先,將task_struct中的標志成員設置為PF_EXITING
  • 其次,調用del_timer_sync()喊出任一內核定時器
  • 如果BSD的進程記賬功能是開啟的,do_exit()調用acct_process()來輸出記賬信息。
  • 然后調用_exit_mm()函數放棄進程占用的mm_struct,如果沒有別的進程使用它們,就徹底釋放它們
  • 接下來調用exit_sem()函數。如果進程排隊等候IPC信號,則離開隊列
  • 調用_exit_files()、_exit_fs()、_exit_namespce()和exit_sighand(),分別遞減文件描述符、文件系統數據、進程名字空間和信號處理函數的引用計數。如果其中某些引用計數的值降為0,那么久代表沒有進程在使用相應的資源,此時可以釋放。
  • 接著把存放在task_struct的exit_code成員中的任務退出碼置為exit()提供的代碼中,或者去完成任何其他由內核機制規定的退出動作
  • 調用exit_notify()向父進程發送信號,將子進程的父進程重新設置為線程組中的其他線程或init進程,并把進程狀態設置為TASK_ZOMBLE
  • 最后,do_exit()調用schedule()切換到其他進程。

至此,與進程相關聯的所有資源都被釋放掉了,只是相關聯的資源被釋放了,進程還有資源沒有被釋放,它剩下所占用的所有資源就是內核棧、thread_info結構和task_struct結構。此時進程存在的唯一目的就是向它的父進程提供信息。父進程檢索到信息后,或者通知內核那是無關的信息后,由進程所持有的剩余內存被釋放,歸還給系統使用。

刪除進程描述符

在調用do_exit()之后,盡管線程已經僵死不能再運行了,但是系統還保留了它的進程描述符。這樣做可以讓系統有辦法在子進程終結后仍能獲得它的信息。
當最終需要釋放進程描述符時,release_task()會被調用,用于完成以下工作:

  • 首先,它調用free_uid()來減少該進程擁有者的進程使用計數。
  • 然后,release_task()調用unhash_process()從pidhash上刪除該進程,同時也要從task_list中刪除該進程
  • 接下來,如果這個進程正在被普通race跟蹤,release_task()將跟蹤進程的父進程重設為最初的父進程并將它從ptrace list上刪除。
  • 最后,release_task()調用put_task_struct()釋放該進程內核棧和thread_info結構所占的頁,并釋放task_struct所占的深藍高速緩存、

至此,進程描述符和所有進程獨享的資源就全部釋放掉了。

孤兒進程造成的進退微谷

如果父進程在子進程之前退出,那么必須有機制來保證子進程能找到一個新的父親,否則的話這些成為孤兒的進程就會在退出時永遠處于僵死狀態,白白的耗費內存。解決方案:子進程在當前線程組(父進程所在的線程組)內找一個線程作為父親,如果不行,就讓init作為它們的父進程。在do_exit()中會調用notify_parent(),該函數會通過forget_original_parent()來執行尋父過程。

static inline void forget_original_parent(struct task_struct * father,struct list_head *to_release)
{struct task_struct *p, *reaper = father;struct list_head *_p, *_n;do {reaper = next_thread(reaper);if (reaper == father) {reaper = child_reaper;break;}} while (reaper->exit_state >= EXIT_ZOMBIE);/** There are only two places where our children can be:** - in our child list* - in our ptraced child list** Search them and reparent children.*/list_for_each_safe(_p, _n, &father->children) {int ptrace;p = list_entry(_p,struct task_struct,sibling);ptrace = p->ptrace;/* if father isn't the real parent, then ptrace must be enabled */BUG_ON(father != p->real_parent && !ptrace);if (father == p->real_parent) {/* reparent with a reaper, real father it's us */choose_new_parent(p, reaper, child_reaper);reparent_thread(p, father, 0);} else {/* reparent ptraced task to its real parent */__ptrace_unlink (p);if (p->exit_state == EXIT_ZOMBIE && p->exit_signal != -1 &&thread_group_empty(p))do_notify_parent(p, p->exit_signal);}/** if the ptraced child is a zombie with exit_signal == -1* we must collect it before we exit, or it will remain* zombie forever since we prevented it from self-reap itself* while it was being traced by us, to be able to see it in wait4.*/if (unlikely(ptrace && p->exit_state == EXIT_ZOMBIE && p->exit_signal == -1))list_add(&p->ptrace_list, to_release);}list_for_each_safe(_p, _n, &father->ptrace_children) {p = list_entry(_p,struct task_struct,ptrace_list);choose_new_parent(p, reaper, child_reaper);reparent_thread(p, father, 1);}
}

先在線程組里找一個線程作為父進程,如果線程組內沒有其他的進程,就將init設為父進程。當合適的父進程找到后,只需要遍歷所有子進程并為它們設置新的父進程。

后面遍歷了兩個鏈表:子進程鏈表和ptrace子進程鏈表。給每個子進程設置新的父進程。當一個進程被跟蹤時,它被暫時設定為調試進程的子進程。此時如果它的父進程退出了,系統會為它和它所有的兄弟重新找一個父進程。在以前的內核中,這就需要遍歷系統所有的進程來找這些子進程,現在的解決辦法是在一個單獨的ptrace跟蹤的子進程鏈表中搜索相關的兄弟進程,用兩個相關鏈表減輕了遍歷帶來的消耗。

5 小結

在本文中,我們討論進程的一般特效,它為何如此重要,以及進程與線程之間的關系。然后,討論了Linux如何存放和表示進程(用task_struct和thread_info),如果創建進程(通過clone()和fork()),如何把新的執行映像裝入到地址空間(通過exec()調用族),如何表示進程的層次關系,父進程又是如何收集后代的信息(通過wait()系統調用族),以及進程最終如何死亡(強制或自愿調用exit())。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379680.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379680.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379680.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JS錯誤代碼解釋大全+VBS錯誤代碼解釋大全

JScript 運行時錯誤 JScript 運行時錯誤是指當 JScript 腳本試圖執行一個系統不能運行的動作時導致的錯誤。當正在運行腳本、計算變量表達式、或者正在動態分配內存時出現 JScript 運行時錯誤時。 錯誤號 描述 5029 數組長度必須為一有限正整數 5030 必須賦給數組長度一個有…

生日蠟燭(藍橋杯)

某君從某年開始每年都舉辦一次生日party&#xff0c;并且每次都要吹熄與年齡相同根數的蠟燭。 現在算起來&#xff0c;他一共吹熄了236根蠟燭。 請問&#xff0c;他從多少歲開始過生日party的&#xff1f; 請填寫他開始過生日party的年齡數。 注意&#xff1a;你提交的應該是…

python日歷模塊_Python日歷模塊| firstweekday()方法與示例

python日歷模塊Python calendar.firstweekday()方法 (Python calendar.firstweekday() Method) firstweekday() method is an inbuilt method of the calendar module in Python. It works on simple text calendars and returns the current setting for the weekday to start…

php 處理 mysql to json, 前臺js處理

public function GetJson(){$query"select * from table";$result mysql_query($query);$rows array();while($row mysql_fetch_array($result)){$rows [] $row;}echo json_encode($rows); } js處理 $.get( "./bll.php", option,function(data ) {var j…

Linux內核設計與實現---進程調度

進程調度1 策略I/O消耗型和處理器消耗型的進程進程優先級時間片進程搶占2 Linux調度算法可執行隊列優先級數組重新計算時間片schedule()計算優先級和時間片睡眠和喚醒負載平衡程序3 搶占和上下文切換用戶搶占內核搶占4 實時5 與調度相關的系統調用與調度策略和優先級相關的系統…

ServletContext(核心內容)

什么是ServletContext對象 ServletContext代表是一個web應用的環境&#xff08;上下文&#xff09;對象&#xff0c;ServletContext對象 內部封裝是該web應用的信息&#xff0c;ServletContext對象一個web應用只有一個 一個web應用有多個servlet對象 ServletContext對象的生…

【轉載】[TC]飛船動畫例子--《C高級實用程序設計》

【聲明和備注】本例子屬于轉載來源于《C高級實用程序設計》&#xff08;王士元&#xff0c;清華大學出版社&#xff09;第11章&#xff0c;菜單設計與動畫技術&#xff0c;第11.5節&#xff0c;一個動畫例子。 本例講解的是在一個繁星背景下&#xff0c;一個由經緯線組成的藍色…

math.sqrt 有問題_JavaScript中帶有示例的Math.SQRT2屬性

math.sqrt 有問題JavaScript | Math.SQRT2屬性 (JavaScript | Math.SQRT2 Property) Math.SQRT2 is a property in math library of JavaScript that is used to find the value of square root of 2. It is generally used to solve problems related to circular figures. Ma…

Linux內核設計與實現---系統調用

系統調用1 API、POSIX和C庫2 系統調用系統調用號3 系統調用處理程序指定恰當的系統調用參數傳遞4 系統調用的實現參數驗證5 系統調用上下文綁定一個系統調用的最后步驟從用戶空間訪問系統調用為什么不通過系統調用的方式實現1 API、POSIX和C庫 API&#xff1a;應用編程接口。一…

內核編譯配置選項含義

Linux 2.6.19.x 內核編譯配置選項簡介 作者&#xff1a;金步國 版權聲明 本文作者是一位自由軟件愛好者&#xff0c;所以本文雖然不是軟件&#xff0c;但是本著 GPL 的精神發布。任何人都可以自由使用、轉載、復制和再分發&#xff0c;但必須保留作者署名&#xff0c;亦不得對聲…

js編碼處理(轉)

js編碼處理(轉) 1. 使用 JS 中的 encodeURIComponent 或 encodeURI 方法。 說明&#xff1a; encodeURIComponent(String) 對傳遞參數進行設置。不編碼字符有 71 個&#xff1a; ! &#xff0c; &#xff0c; ( &#xff0c; ) &#xff0c; * &#xff0c; - &#…

手動去設置HTTP響應行、響應頭、響應體

①手動去設置HTTP響應行中的狀態碼&#xff0c;這里用到了response的setStatus(int sc);這個方法 package com.itheima.line;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…

Java SecurityManager checkListen()方法與示例

SecurityManager類的checkListen()方法 (SecurityManager Class checkListen() method) checkListen() method is available in java.lang package. checkListen()方法在java.lang包中可用。 checkListen() method invokes checkPermission with the given SocketPermission(&q…

基本的二分查找、尋找第一個和最后一個數的二分查找

二分查找1 二分查找的框架2 尋找一個數&#xff08;基本的二分搜索&#xff09;3 尋找左側邊界的二分搜索4 尋找右側邊界的二分查找5 合并二分查找場景&#xff1a;有序數組尋找一個數、尋找左側邊界&#xff08;有序數組第一個等目標數的下標&#xff09;、尋找右側邊界&#…

PostgreSQL 中的遞歸查詢 與oracle 的比較

PostgreSQL 中的遞歸查詢&#xff0c;2種方法&#xff1a; 1、用with decursive WITH RECURSIVE d AS (SELECT d1.id,d1.parent_id,d1.caption FROM course_types d1 where d1.dr 0 and d1.idtypeId union ALL SELECT d2.id,d2.parent_id,d2.caption FROM course_types d2, d …

教你如何玩轉GitHub

使用GitHub ①目的&#xff1a;借助GitHub托管項目代碼 基本概念&#xff1a; ①倉庫(Repository)&#xff1a; 用來存放項目代碼&#xff0c;每個項目對應一個倉庫&#xff0c;多個開源項目對應多個倉庫 ②收藏(Star)&#xff1a; 收藏項目&#xff0c;方便下次查看 ③…

Java SecurityManager checkDelete()方法與示例

SecurityManager類的checkDelete()方法 (SecurityManager Class checkDelete() method) checkDelete() method is available in java.lang package. checkDelete()方法在java.lang包中可用。 checkDelete() method calls checkPermission with FilePermission(filename,"d…

jQuery中的treeview插件

jQuery做樹狀結構真的很簡單,下面做一個最簡單的示例: 在html文件中引用: <link rel"stylesheet" href"../jquery.treeview.css" /> <link rel"stylesheet" href"../red-treeview.css" /> <link rel"styles…

Linux內核設計與實現---中斷和中斷處理程序

中斷和中斷處理程序1 中斷異常2 中斷處理程序上半部與下半部的對比3 注冊中斷處理程序釋放中斷處理程序4 編寫中斷處理程序重入和中斷處理程序共享的中斷處理程序中斷處理程序實例5 中斷上下文6 中斷處理機制的實現7 中斷控制禁止和激活中斷禁止指定中斷線中斷系統的狀態8 總結…

asp.net中的窗體身份驗證(最簡單篇)

在創建網站中&#xff0c;常常會使用到身份驗證。asp.net中內置了幾種身份驗證的方式&#xff0c;如Windows、Froms、Passport等。這幾種身份驗證的方式各有不同。一般來說&#xff0c;網站的身份驗證方式都會經過以下幾個步驟&#xff1a; 1、輸入用戶名和密碼&#xff0c;單擊…