Process Management 2

Process Family Tree

All process are descendants of the init process, whose pid = 1. The init process, in turn, reads the system initscripts and executes more programs, create more child process to finsh the boot .

The relationship between processes is stored in the process descriptor. Each task_struct has a pointer to the parent’s task_struct , named parent , and a list of children, named children.

We can iterate over a process'children with the code below

struct task_struct *task;

struct list_head *list;

list_for_each(list, &current->children) {

task = list_entry(list, struct task_struct, sibling);

/* task now points to one of current’s children */

}

We also can get init process pointer with the code below:

struct task_struct *task;
for (task = current; task != &init_task; task = task->parent);
/* task now points to init */

In fact, you can follow the process hierarchy from any one process in the system to any
other. Oftentimes, however, it is desirable simply to iterate over all processes in the system.
This is easy because the task list is a circular, doubly linked list.To obtain the next task or previous task in the list, given any valid task, use

list_entry(task->tasks.next, struct task_struct, tasks);
list_entry(task->tasks.prev, struct task_struct, tasks);

These two routines are provided by the macros next_task(task) and prev_task(task).
Finally, the macro for_each_process(task) is provided, which iterates over the entire task list. On each iteration, task points to the next task in the list.

struct task_struct *task;
for_each_process(task) {
/* this pointlessly prints the name and PID of each task */
  printk(“%s[%d]\n”, task->comm, task->pid);
}

Process Creation

Copy-on-Write

Linux use fork() to create new process just duplicating the parent. This approach is naive and inefficient since it copies much data that might otherwise be shared. Worse still, if the new process were to immediately execute a new image, all the copying is wasted. So fork() is actually implemented through a mechinism called copy-on-write pages. COW is a tech to delay or altogether prevent copying of the data. Rather than duplicate the process address space, the parent and the child can share a single copy. The duplication of resource occurs only when they are written; until then, they are shared read-only. This technique delays the copying of each pages until it is written to. In the case that the pages are never written--- for example in the common sense, if exec() is called immediately after fork() ---- they never need to be copied. The only overhead incurred by fork() is the duplication of the parent’s page tables and the creation of a unique process descriptor for the child. This is an important optimization because the Unix philosophy encourages quick process execution.

fork()

Linux implements fork() via the clone() system call.This call takes a series of flags that specify which resources, if any, the parent and child process should share. The clone() system call, in turn, calls do_fork() .

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    /*
     * Do some preliminary argument and permissions checking before we
     * actually start allocating stuff
     */
    if (clone_flags & CLONE_NEWUSER) {
        if (clone_flags & CLONE_THREAD)
            return -EINVAL;
        /* hopefully this check will go away when userns support is
         * complete
         */
        if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) ||
                !capable(CAP_SETGID))
            return -EPERM;
    }

    /*
     * When called from kernel_thread, don't do user tracing stuff.
     */
    if (likely(user_mode(regs)))
        trace = tracehook_prepare_clone(clone_flags);

    p = copy_process(clone_flags, stack_start, regs, stack_size,
             child_tidptr, NULL, trace);
    /*
     * Do this prior waking up the new thread - the thread pointer
     * might get invalid after that point, if the thread exits quickly.
     */
    if (!IS_ERR(p)) {
        struct completion vfork;

        trace_sched_process_fork(current, p);

        nr = task_pid_vnr(p);

        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);

        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
        }

        audit_finish_fork(p);
        tracehook_report_clone(regs, clone_flags, nr, p);

        /*
         * We set PF_STARTING at creation in case tracing wants to
         * use this to distinguish a fully live task from one that
         * hasn't gotten to tracehook_report_clone() yet.  Now we
         * clear it and set the child going.
         */
        p->flags &= ~PF_STARTING;

        wake_up_new_task(p, clone_flags);

        tracehook_report_clone_complete(trace, regs,
                        clone_flags, nr, p);

        if (clone_flags & CLONE_VFORK) {
            freezer_do_not_count();
            wait_for_completion(&vfork);
            freezer_count();
            tracehook_report_vfork_done(p, nr);
        }
    } else {
        nr = PTR_ERR(p);
    }
    return nr;
}

This function calls copy_process(). We can figure out what copy_process do as below.

  1. It calls dup_task_struct() , which creates a new kernel stack, thread_info structure, and task_struct for the new process.The new values are identical to those of the current task.At this point, the child and parent process descriptors are identical.
  2. It then checks that the new child will not exceed the resource limits on the number of processes for the current user.
  3. The child needs to differentiate itself from its parent.Various members of the
    process descriptor are cleared or set to initial values. Members of the process
    descriptor not inherited are primarily statistically information.The bulk of the values in task_struct remain unchanged.
  4. The child’s state is set to TASK_UNINTERRUPTIBLE to ensure that it does not yet run.
  5. copy_process() calls copy_flags() to update the flags member of the task_struct .The PF_SUPERPRIV flag, which denotes whether a task used superuser privileges, is cleared.The PF_FORKNOEXEC flag, which denotes a process that has not called exec() , is set.
  6. It calls alloc_pid() to assign an available PID to the new task.
  7. Depending on the flags passed to clone() , copy_process() either duplicates or shares open files, filesystem information, signal handlers, process address space, and namespace.These resources are typically shared between threads in a given process; otherwise they are unique and thus copied here.
  8. Finally, copy_process() cleans up and returns to the caller a pointer to the new child.
    Back in do_fork() , if copy_process() returns successfully, the new child is woken up and run. Deliberately, the kernel runs the child process first. 8 In the common case of the child simply calling exec() immediately, this eliminates any copy-on-write overhead that would occur if the parent ran first and began writing to the address space.

vfork()

The vfork() system call has the same effect as fork() , except that the page table entries of the parent process are not copied. Instead, the child executes as the sole thread in the parent’s address space, and the parent is blocked until the child either calls exec() or exits. The child is not allowed to write to the address space.Today, with copy-on-write and child-runs-first semantics, the only benefit to vfork() is not copying the parent page tables entries.

Thread

Linux has a unique implementation of threads.To the Linux kernel, there is no concept of a thread. Linux implements all threads as standard processes.The Linux kernel does not provide any special scheduling semantics or data structures to represent threads. Instead, a thread is merely a process that shares certain resources with other processes. Each thread has a unique task_struct and appears to the kernel as a normal process— threads just happen to share resources, such as an address space, with other processes.

Creating Thread

Threads are created the same as normal tasks, with the exception that the clone() system call is passed flags corresponding to the specific resources to be shared:

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

The previous code results in behavior identical to a normal fork() , except that the address space, filesystem resources, file descriptors, and signal handlers are shared. In other words, the new task and its parent are what are popularly called threads.
In contrast, a normal fork() can be implemented as

clone(SIGCHLD, 0);

The flags provided to clone() help specify the behavior of the new process and detail what resources the parent and child will share.The table below lists the clone flags, which are defined in <linux/sched.h> , and their effect.


image.png

Kernel Thread

  1. Exist and operate solely in kernel space.
  2. Do NOT have a address space: *mm pointer, which point to address space, is NULL.
    3.Kernel threads are created on system boot by other kernel threads. Indeed, a kernel thread can be created only by another kernel thread.

Process Termination

It occurs when the process calls the exit() system call, The bulk of the work is handled by do_exit() ,defined in kernel/exit.c , which completes a number of chores:

  1. It sets the PF_EXITING flag in the flags member of the task_struct .
  2. It calls del_timer_sync() to remove any kernel timers. Upon return, it is guaranteed that no timer is queued and that no timer handler is running.
  3. If BSD process accounting is enabled, do_exit() call acct_update_integrals()
    to write out accounting information.
  4. It calls exit_mm() to release the mm_struct held by this process. If no other process is using this address space—that it, if the address space is not shared—the kernel then destroys it.
  5. It calls exit_sem() . If the process is queued waiting for an IPC semaphore, it is dequeued here.
  6. It then calls exit_files() and exit_fs() to decrement the usage count of objects related to file descriptors and filesystem data, respectively. If either usage counts reach zero, the object is no longer in use by any process, and it is destroyed.
  7. It sets the task’s exit code, stored in the exit_code member of the task_struct , to the code provided by exit() or whatever kernel mechanism forced the termination.The exit code is stored here for optional retrieval by the parent.
  8. It calls exit_notify() to send signals to the task’s parent, reparents any of the task’s children to another thread in their thread group or the init process, and sets the task’s exit state, stored in exit_state in the task_struct structure, to EXIT_ZOMBIE .
  9. calls schedule() to switch to a new process (see Chapter 4). Because
    the process is now not schedulable, this is the last code the task will ever execute. do_exit() never returns.

At this point, all objects associated with the task are freed. The task is not runnable and is in the EXIT_ZOMBIE exit state. The only memory it occupies is its kernel stack, the thread_info structure, and the task_struct structure. The task exists solely to provide information to its parent.After the parent retrieves the information, or notifies the kernel that it is uninterested, the remaining memory held by the process is freed and returned to the system for use.

Removing the Process Descripter

After the parent has obtained information on its terminated child, or signified to the kernel that it does not care, the child’s task_struct is deallocated.
When it is time to finally deallocate the process descriptor, release_task() is invoked. It does the following:

  1. It calls __exit_signal() , which calls __unhash_process() , which in turns calls detach_pid() to remove the process from the pidhash and remove the process from the task list.
    2.__exit_signal() releases any remaining resources used by the now dead process
    and finalizes statistics and bookkeeping.
  2. If the task was the last member of a thread group, and the leader is a zombie, then release_task() notifies the zombie leader’s parent.
  3. release_task() calls put_task_struct() to free the pages containing the
    process’s kernel stack and thread_info structure and deallocate the slab cache containing the task_struct .

At this point, the process descriptor and all resources belonging solely to the process has been freed.

Reparenting of parentless task.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容