"Linux Kernel Design and Implementation" Reading Notes-Process Management

process

A process is a program in the execution period.

The process also includes other resources, such as open files, pending signals, kernel internal data, processor status, one or more memory-mapped memory address spaces and one or more execution threads, as well as storing global Variable data segment, etc.

Therefore, the process is the general term for the programs and related resources in the execution period .

Another name for a process is task.

 

Thread

The thread is the active object in the process.

Each thread has an independent program counter, process stack and a set of process registers.

The Linux system does not distinguish between threads and processes .

Linux implements all threads as processes.

A process is simply regarded as a process that shares some resources with other processes.

 

Process related system calls

Implement system call has been added SYS _x, the X-system call name

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

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

Create a brand new process by copying an existing process.

The fork() system call returns from the kernel twice: once to the parent process, and again to the newly spawned child process.

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);
}

The book says that Linux implements fork() through the clone() system call, but it is not.

Both are achieved through do_fork().

do_fork() completes most of the work of process creation, and it is located in 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 creates a new address space and loads the new program into it.

exec actually has a set of functions, not one.

The kernel system calls are execve(), execlp(), execle(), execv() and execvp().

exit():(kernel\exit.c)

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

The program exits execution.

After the process exits execution, it is set to a dead state until its parent process calls wait() or 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;
}

The parent process can call wait4() to query whether the child process is terminated.

The kernel is responsible for implementing the wait4() system call. Linux usually needs to provide wait(), waitpid(), wait3() and wait4() functions through the C library.

The init process will routinely call wati() to check its child processes and remove all dead processes related to it.

 

Process descriptor

Most of the processing code in the kernel is directly carried out through task_struct .

The kernel puts the list of processes in a two-way circular linked list called task queue . Each item in the linked list is of type task_struct, which is a structure called process descriptor.

The process descriptor contains all the information of a specific process.

The process descriptor is accessed through another structure thread_info structure (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;
};

Two stacks are created when a process is created, one of which is the kernel stack.

This thread_info structure is created at the bottom of the stack (for a stack that grows downward) or at the top of the stack (for a stack that grows upward).

Its member task points to the task_struct structure of the process.

A macro (arch\x86\include\asm\current.h) is defined in Linux to directly access the current process:

#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 */

 

Process status

as the picture shows:

TASK_RUNNING : The process is executable: it is either executing or waiting for execution in the run queue.

TASK_INTERRUPTIBLE : The process is sleeping (that is, it is blocked), waiting for the fulfillment of certain conditions.

TASK_UNINTERRUPTIBLE : This state is the same as the TASK_INTERRUPTIBLE state except that it will not be awakened or ready to be put into operation even if it receives a signal.

__TASK_TRACED : Process tracked by other processes.

__TASK_STOPPED : The process stops executing. It usually occurs when signals such as SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU are received.

Corresponding code:

/*
 * 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

 

Kernel thread

Kernel threads are standard processes that run independently in the kernel space.

There is no independent address space for kernel threads.

The kernel process can be scheduled and preempted just like a normal process.

 

Guess you like

Origin blog.csdn.net/jiangwei0512/article/details/106029889