进程管理1——进程和线程的差异

一、创建传参差异

1. 线程创建

/*
pthread_create //glibc
    __pthread_create_2_1 
        create_thread
            do_clone
                clone //系统调用
*/
static int create_thread (struct pthread *pd, ...)
{
    /* x86上的传参,非Arm64的 */
    int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
            | CLONE_SETTLS | CLONE_PARENT_SETTID
            | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
            | 0);

    int ret = do_clone (pd, attr, clone_flags, start_thread, STACK_VARIABLES_ARGS, 1);
    ...
}

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
    int __user *, parent_tidptr, unsigned long, tls, int __user *, child_tidptr)
{
    struct kernel_clone_args args = {
        .flags        = (lower_32_bits(clone_flags) & ~CSIGNAL),
        .pidfd        = parent_tidptr,
        .child_tid    = child_tidptr,
        .parent_tid    = parent_tidptr,
        .exit_signal    = (lower_32_bits(clone_flags) & CSIGNAL),
        .stack        = newsp,
        .tls        = tls,
    };

    return kernel_clone(&args);
}         

调用 pthread_create()创建线程时的标记有 CLONE_VM、CLONE_FS、CLONE_FILES、CLONE_SIGNAL、CLONE_SETTLS、CLONE_PARENT_SETTID、CLONE_CHILD_CLEARTID、CLONE_SYSVSEM。

 

2. 进程创建

SYSCALL_DEFINE0(fork) //kernel/fork.c
{
    struct kernel_clone_args args = {
        .exit_signal = SIGCHLD, //.flags 一个标志也没有设置
    };

    return kernel_clone(&args);
}

通过fork系统调用创建进程时一个CLONE标志都没有指定。

SYSCALL_DEFINE0(vfork) //kernel/fork.c
{
    struct kernel_clone_args args = {
        .flags        = CLONE_VFORK | CLONE_VM,
        .exit_signal    = SIGCHLD,
    };

    return kernel_clone(&args);
}

通过vfork系统调用创建进程时指定了 CLONE_VM 标志,两个进程会共享地址空间。

 

3. 内核线程创建

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
    struct kernel_clone_args args = {
        .flags        = ((lower_32_bits(flags) | CLONE_VM | CLONE_UNTRACED) & ~CSIGNAL),
        .exit_signal    = (lower_32_bits(flags) & CSIGNAL),
        .stack        = (unsigned long)fn,
        .stack_size    = (unsigned long)arg,
    };

    return kernel_clone(&args);
}

内核线程至少包含 CLONE_VM 标记,都是共享地址空间的。

 

4. 创建时传参差异

创建进程时的flag:一个CLONE标志都没有指定
创建线程时的flag:CLONE_VM、CLONE_FS、CLONE_FILES、CLONE_SIGNAL、CLONE_SETTLS、CLONE_PARENT_SETTID、CLONE_CHILD_CLEARTID、CLONE_SYSVSEM。
创建内核线程时的flag:至少包含 CLONE_VM

其中:
CLONE_VM: 新task和父进程共享地址空间。
CLONE_FS:新task和父进程共享文件系统信息。
CLONE_FILES:新task和父进程共享文件描述符表。

 

二、task_struct 初始化差异

1. copy_process 实现

pid_t kernel_clone(struct kernel_clone_args *args)
{
    p = copy_process(NULL, trace, NUMA_NO_NODE, args);
}

struct task_struct *copy_process(struct pid *pid, int trace, int node, struct kernel_clone_args *args)
{
    struct task_struct *p;

    /* 1. 复制进程 task_struct 结构体 */
    p = dup_task_struct(current, node);

    /* 2. 拷贝 files_struct */
    retval = copy_files(clone_flags, p);
    /* 3. 拷贝 fs_struct */
    retval = copy_fs(clone_flags, p);
    /* 4. 拷贝 mm_struct */
    retval = copy_mm(clone_flags, p);
    
    ...
    return p;
}

 

2. dup_task_struct()

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
    struct task_struct *tsk;
    
    err = arch_dup_task_struct(tsk, orig);

    tsk->stack = stack;
    tsk->stack_vm_area = stack_vm_area;
    refcount_set(&tsk->stack_refcount, 1);
    ...

    return tsk;
}

int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
{
    /* 直接结构体值拷贝 */
    *dst = *src;
}

直接进行值拷贝,浅拷贝,后续没有覆盖赋值的话,新建任务 p 的 task_struct 结构就和 current 是一样的。


3. copy_files()

static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
{
    struct files_struct *oldf, *newf;
    int error = 0;

    /* 若是指定了 CLONE_FILES,直接共用current的 */
    if (clone_flags & CLONE_FILES) {
        atomic_inc(&oldf->count);
        goto out;
    }

    /* 若没有指定 CLONE_FILES 才会拷贝一份 */
    newf = dup_fd(oldf, NR_OPEN_MAX, &error);
    if (!newf)
        goto out;
    tsk->files = newf;
out:
    return error;
}

若指定了 CLONE_FILES 标志,就直接复用 current 进程的 files_struct 结构体。

 

4. copy_fs()

static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
    struct fs_struct *fs = current->fs;

    /* 若是指定了 CLONE_FS,直接共用current的 */
    if (clone_flags & CLONE_FS) {
        fs->users++;
        return 0;
    }

    /* 若没有指定 CLONE_FS 才会拷贝一份 */
    tsk->fs = copy_fs_struct(fs);

    return 0;
}

若指定了 CLONE_FS 标志,就直接复用 current 进程的 fs_struct 结构体。


4. copy_mm()

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
    struct mm_struct *mm, *oldmm;
    int retval;

    tsk->mm = NULL;
    tsk->active_mm = NULL;
    oldmm = current->mm;

    /* 若是指定了 CLONE_VM,直接共用current的 */
    if (clone_flags & CLONE_VM) {
        mmget(oldmm);
        mm = oldmm;
        goto good_mm;
    }

    /* 若没有指定 CLONE_VM 才会拷贝一份 */
    mm = dup_mm(tsk, current->mm);

good_mm:
    tsk->mm = mm;
    tsk->active_mm = mm;

    return 0;
}

若指定了 CLONE_VM 标志,就直接复用 current 进程的 mm_struct 结构体。创建线程和内核线程时都指定了这个标志。

 

三、总结

1. 内核中线程和进程都是用 task_struct 来表示,只不过线程和创建它的父进程共享打开文件列表、目录信息、虚拟地址空间等数据结构,会更轻量一些。所以也叫轻量级进程。

2. 对于内核任务,都指定了 CLONE_VM 标志,致使其使用地址空间都是同一个,所以一般都叫内核线程,而不是内核进程。

 

posted on 2022-10-13 15:06  Hello-World3  阅读(123)  评论(0编辑  收藏  举报

导航