进程管理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 阅读(131) 评论(0) 编辑 收藏 举报