《Linux内核设计与实现》读书笔记——进程管理

进程

进程是处于执行期的程序。

进程还要包括其它资源,如打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程,还包括用来存放全局变量的数据段等。

所以,进程是处于执行期的程序以及相关资源的总称

进程的另一个名字是任务(task)。

 

线程

线程是进程中的活动对象。

每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。

Linux系统对线程和进程并不特别区分

Linux把所有的线程都当成进程来实现。

进程仅仅被视为一个与其它进程共享某些资源的进程。

 

进程相关的系统调用

系统调用的实现中增加了sys_x,x是系统调用名

fork():(arch\x86\kernel\process.c)

int sys_fork(struct pt_regs *regs)
{
    return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}

通过复制一个现有的进程来创建一个全新的进程。

fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。

clone():(arch\x86\kernel\process.c)

long
sys_clone(unsigned long clone_flags, unsigned long newsp,
      void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
    if (!newsp)
        newsp = regs->sp;
    return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}

书中说Linux通过clone()系统调用实现fork(),但是实际上并不是。

两者都通过do_fork()来实现。

do_fork()完成进程创建的大部分工作,它位于kernel\fork.c。

exec:(arch\x86\kernel\process.c)

long sys_execve(char __user *name, char __user * __user *argv,
        char __user * __user *envp, struct pt_regs *regs)
{
    long error;
    char *filename;
    filename = getname(name);
    error = PTR_ERR(filename);
    if (IS_ERR(filename))
        return error;
    error = do_execve(filename, argv, envp, regs);
#ifdef CONFIG_X86_32
    if (error == 0) {
        /* Make sure we don't return using sysenter.. */
                set_thread_flag(TIF_IRET);
        }
#endif
    putname(filename);
    return error;
}

exec创建新的地址空间,并把新的程序载入其中。

exec实际上有一组函数,而不是一个。

内核系统调用有execve()、execlp()、execle()、execv()和execvp()。

exit():(kernel\exit.c)

SYSCALL_DEFINE1(exit, int, error_code)
{
    do_exit((error_code&0xff)<<8);
}

程序退出执行。

进程退出执行后被设置成僵死状态,直到它的父进程调用wait()或waitpid()为止。

wait4():(kernel\exit.c)

SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
        int, options, struct rusage __user *, ru)
{
    struct wait_opts wo;
    struct pid *pid = NULL;
    enum pid_type type;
    long ret;
    if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|
            __WNOTHREAD|__WCLONE|__WALL))
        return -EINVAL;
    if (upid == -1)
        type = PIDTYPE_MAX;
    else if (upid < 0) {
        type = PIDTYPE_PGID;
        pid = find_get_pid(-upid);
    } else if (upid == 0) {
        type = PIDTYPE_PGID;
        pid = get_task_pid(current, PIDTYPE_PGID);
    } else /* upid > 0 */ {
        type = PIDTYPE_PID;
        pid = find_get_pid(upid);
    }
    wo.wo_type  = type;
    wo.wo_pid   = pid;
    wo.wo_flags = options | WEXITED;
    wo.wo_info  = NULL;
    wo.wo_stat  = stat_addr;
    wo.wo_rusage    = ru;
    ret = do_wait(&wo);
    put_pid(pid);
    /* avoid REGPARM breakage on x86: */
    asmlinkage_protect(4, ret, upid, stat_addr, options, ru);
    return ret;
}

父进程可以调用wait4()查询子进程是否终结。

内核负责实现wait4()系统调用。Linux通过C库通常需要提供wait()、waitpid()、wait3()和wait4()函数。

init进程会例行调用wati()来检查其子进程,清除所有与其相关的僵死进程。

 

进程描述符

内核中大部分处理进程的代码都是直接通过task_struct进行的。

内核把进程的列表放在叫做任务队列的双向循环链表中,链表中的每一项是类型为task_struct,称为进程描述符的结构。

进程描述符中包含一个具体进程的所有信息。

进程描述符通过另外一个结构体thread_info结构体访问(arch\x86\include\asm\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;  /* 0 => preemptable,
                           <0 => BUG */
    mm_segment_t        addr_limit;
    struct restart_block    restart_block;
    void __user     *sysenter_return;
#ifdef CONFIG_X86_32
    unsigned long           previous_esp;   /* ESP of the previous stack in
                           case of nested (IRQ) stacks
                        */
    __u8            supervisor_stack[0];
#endif
    int         uaccess_err;
};

在创建进程时会创建两个栈,其中一个就是内核栈。

在栈底(对于向下增长的栈来说)或栈顶(对于向上增长的栈来说)会创建这个thread_info结构体。

它的成员task就指向了进程的task_struct结构体。

Linux中定义了一个宏(arch\x86\include\asm\current.h)可以直接访问当前进程:

#ifndef _ASM_X86_CURRENT_H
#define _ASM_X86_CURRENT_H

#include <linux/compiler.h>
#include <asm/percpu.h>

#ifndef __ASSEMBLY__
struct task_struct;

DECLARE_PER_CPU(struct task_struct *, current_task);

static __always_inline struct task_struct *get_current(void)
{
	return percpu_read_stable(current_task);
}

#define current get_current()

#endif /* __ASSEMBLY__ */

#endif /* _ASM_X86_CURRENT_H */

 

进程状态

如图所示:

TASK_RUNNING:进程可执行的:它或者正在执行,或者在运行队列中等待执行。

TASK_INTERRUPTIBLE:进程正在睡眠中(也就是说他被阻塞),等待某些条件的达成。

TASK_UNINTERRUPTIBLE:除了就算是接受到信号也不会被唤醒或准备投入运行之外,这个状态与TASK_INTERRUPTIBLE状态相同。

__TASK_TRACED:被其他进程跟踪的进程。

__TASK_STOPPED:进程停止执行。通常发生在接收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。

对应的代码:

/*
 * Task state bitmask. NOTE! These bits are also
 * encoded in fs/proc/array.c: get_task_state().
 *
 * We have two separate sets of flags: task->state
 * is about runnability, while task->exit_state are
 * about the task exiting. Confusing, but this way
 * modifying one set can't modify the other one by
 * mistake.
 */
#define TASK_RUNNING        0
#define TASK_INTERRUPTIBLE  1
#define TASK_UNINTERRUPTIBLE    2
#define __TASK_STOPPED      4
#define __TASK_TRACED       8

 

内核线程

内核线程是独立运行在内核空间的标准进程。

内核线程没有独立的地址空间。

内核进程和普通进程一样,可以被调度,也可以被抢占。

 

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/106029889