当前位置: 首页 > news >正文

Linux 进程的一生(一):进程与线程的创建机制解析

在 Linux 操作系统中,每个任务都以「进程」的形式存在。但 Linux 下的「线程」又是什么?Linux 并没有单独定义一种全新数据结构来表示线程,而是将线程视为一种特殊的进程——一种共享资源的轻量级进程。然而,在具体实现和运行机制上,线程与进程又存在诸多关键差异,这也使得理解二者的区别和联系非常重要。

进程和线程共享许多核心机制,但在创建、调度、资源共享、同步和消亡等环节又体现出微妙的不同。例如:

  • 进程通常通过fork()系统调用创建,而线程更多地使用pthread_create(),但在内核中二者最终都归结到kernel_clone方法。
  • 线程共享内存空间、文件描述符等资源,但仍有私有的数据,如线程局部存储(TLS)。
  • 线程在同步机制上广泛使用futex原语来实现互斥锁和条件变量,体现出与进程级同步不同的性能优势。

为了深入理解进程与线程的生命周期,本系列博文将通过 Linux 内核6.12与 glibc 库的源码,详细探索如下关键问题:

  • 进程与线程各自如何诞生?它们的创建方式在内核和glibc库中有哪些异同?
  • 内核如何调度和管理进程与线程?为何说线程调度和进程调度实际上共享同一套机制?
  • 线程和进程如何实现资源共享和隔离?同步、阻塞机制又如何在其中发挥作用?
  • 最终,进程和线程如何结束自己的生命周期?内核又如何回收它们各自的资源?

1.进程和线程的概念

在探讨进程与线程的区别时,人们往往更关注它们在概念和资源管理上的差异。但实际上,在 Linux 内核中,进程与线程之间的相同点远远多于不同点。虽然经典操作系统理论中,我们通常会将进程抽象为进程控制块(Process Control Block, PCB),线程抽象为线程控制块(Thread Control Block, TCB),但 Linux 内核却做了更统一、更精巧的设计:Linux 内核统一使用了一个核心的数据结构——task_struct,来同时表示进程和线程。

在 Linux 内核源码中,task_struct结构定义如下(节选部分关键字段):

struct task_struct {
    // 1. 进程或线程的当前状态(运行、阻塞、停止等)
    unsigned int			__state;
    ......

    // 2. 进程或线程的优先级信息,用于内核调度
    int				prio;
    int				static_prio;
    int				normal_prio;
    unsigned int			rt_priority;

    // 3. 进程或线程的地址空间描述符(虚拟内存管理)
    struct mm_struct		*mm;
    struct mm_struct		*active_mm;

    // 4. 进程和线程的标识(PID与线程组ID)
    pid_t				pid;
    pid_t				tgid;

    // 5. 进程或线程之间的父子、兄弟关系,用于构成进程树
    struct task_struct __rcu	*parent;
    struct list_head		children;
    struct list_head		sibling;
    struct task_struct		*group_leader;

    // 6. 进程或线程的文件系统上下文(根目录、当前工作目录等)
    struct fs_struct		*fs;

    // 7. 进程或线程打开的文件描述符信息
    struct files_struct		*files;

    // 8. 进程或线程所在的各种命名空间(网络、用户、PID等)
    struct nsproxy			*nsproxy;
    ......
};

通过task_struct这个统一的数据结构,Linux 内核巧妙地封装并统一管理了进程和线程所涉及的众多资源。其中存在少数字段体现出进程与线程在内核管理上的差异。理解这些差异化的成员,有助于我们更清晰地认识 Linux 如何高效而灵活地实现进程与线程的统一管理。

下面,我们着重分析能体现进程与线程差异的关键成员。

1.1 进程、线程ID

我们知道,每个进程或线程在 Linux 内核中都有一个唯一的标识 ID。在 task_struct 结构体中,两个关键字段——pidtgid——用于表示进程或线程的身份。

然而,对于进程和线程而言,这两个字段的含义有所不同:

  • 对于进程pidtgid是相同的。即,一个普通的进程,它的 pid 就是 tgid,代表整个进程的唯一标识。
  • 对于线程:Linux 线程本质上是通过 clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND) 方式创建的轻量级进程。在同一进程内的多个线程:
    • 每个线程都有自己的 pid,用于内核内部调度。
    • 但它们共享相同的 tgid,即所有线程的 tgid 都等于主线程的 pid,表示它们属于同一个线程组。

图示如下,对用户程序来说,调用getpid方法返回的其实是tgid。:

在这里插入图片描述

1.2 进程、线程地址空间

在 Linux 内核中,mm_struct 结构体用于表示进程的虚拟内存空间,它是进程地址空间管理的核心数据结构。每个普通进程都会有一个独立的 mm_struct,用于记录其虚拟地址空间的布局,包括代码段、数据段、堆、栈等。而对于线程,由于线程共享进程的地址空间,因此多个线程共用同一个 mm_struct

struct mm_struct {
    unsigned long mmap_base;	/* base of mmap area */
    unsigned long mmap_legacy_base;	/* base of mmap area in bottom-up allocations */
    unsigned long start_code, end_code, start_data, end_data;//代码段及数据段的开头和结尾
    unsigned long start_brk, brk, start_stack;//堆内存和用户态堆栈的起始地址
    unsigned long arg_start, arg_end, env_start, env_end;//进程命令行参数 (argv[]) 在地址空间中的位置
    
    .......
}

进程的 mm_struct 组织了进程运行所需的所有内存资源,共同表示一个虚拟地址空间。

但是对于内核线程来说,又有不同点,由于其只工作在地址空间比较高的地方,所以不涉及对虚拟内存部分的使用,故其mm_struct是NULL。

在这里插入图片描述

另一个字段是active_mm,该字段表示当前任务(进程或线程)在内核态执行时所使用的地址空间。它的存在主要是为了支持内核线程,因为内核线程本身没有用户态的 mm_struct,但在某些情况下仍需要访问用户态的地址空间,比如进行 copy_from_user()copy_to_user() 操作。

字段适用于作用
mm进程(普通用户态任务)指向该进程的 mm_struct,管理进程的地址空间
mm用户态线程线程共享进程地址空间,因此 mm 指向同一 mm_struct
mm内核线程NULL,因为内核线程没有用户态地址空间
active_mm进程等同于 mm,即 active_mm == mm
active_mm内核线程继承上一个运行的普通进程的 mm_struct,用于访问用户地址空间

2.进程的创建

在这里插入图片描述

进程的创建中最核心的就是fork系统调用。其源代码实现如下:

SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
    struct kernel_clone_args args = {
        .exit_signal = SIGCHLD,  // 子进程退出时发送 SIGCHLD 信号给父进程
    };

    return kernel_clone(&args);
#else
    return -EINVAL;  // 如果系统不支持 MMU,则返回错误
#endif
}
struct kernel_clone_args {
	u64 flags;
......
}

这里传入的args,其实是传入了一个flag选项。可选的值如下:

进程/线程创建相关标志

宏定义作用
CSIGNAL0x000000ff子进程退出时向父进程发送的信号掩码(通常是 SIGCHLD
CLONE_VM0x00000100共享内存地址空间(进程间不会复制 mm_struct,用于线程)
CLONE_FS0x00000200共享文件系统信息chdir/chroot 影响所有线程)
CLONE_FILES0x00000400共享文件描述符表(多个线程可使用相同 fd
CLONE_SIGHAND0x00000800共享信号处理机制(信号处理函数共享)
CLONE_PIDFD0x00001000创建 pidfd(进程文件描述符),可用于进程管理
CLONE_PTRACE0x00002000子进程继承父进程的 ptrace 追踪状态(调试相关)
CLONE_VFORK0x00004000使用 vfork() 语义(子进程执行 exec() 之前,父进程阻塞)
CLONE_PARENT0x00008000让子进程与父进程共享相同的父进程(更改 ppid
CLONE_THREAD0x00010000使子进程与父进程处于同一线程组tgid 相同,即 pthread_create() 线程)

Namespace(命名空间)隔离相关标志

宏定义作用
CLONE_NEWNS0x00020000新挂载命名空间(独立 mount/umount
CLONE_NEWCGROUP0x02000000cgroup 命名空间(独立的 cgroup 控制组)
CLONE_NEWUTS0x04000000新 UTS(主机名)命名空间(隔离 hostnamedomainname
CLONE_NEWIPC0x08000000新 IPC(进程间通信)命名空间System V 共享内存、消息队列、信号量独立)
CLONE_NEWUSER0x10000000新用户命名空间(允许非 root 用户拥有 root 权限)
CLONE_NEWPID0x20000000新进程 ID 命名空间(进程 PID 独立,适用于容器)
CLONE_NEWNET0x40000000新网络命名空间(独立 IP网络接口端口
CLONE_NEWTIME0x00000080新时间命名空间(独立 CLOCK_REALTIMECLOCK_MONOTONIC

但是在fork进程的时候,只传递了一个SIGCHLD,这就意味着在fork进程时,虚拟地址空间、文件系统信息、文件列表都要创建新的。而这些与命名空间隔离的都会与父进程共用。

kernel_clone方法:

pid_t kernel_clone(struct kernel_clone_args *args)
{
    u64 clone_flags = args->flags;  // 解析 clone 标志位
    struct completion vfork;        // 用于 vfork 机制的同步
    struct pid *pid;                // 新进程的 PID 结构
    struct task_struct *p;          // 新进程的 task_struct
    int trace = 0;                  // Ptrace 事件类型
    pid_t nr;                       // 最终返回的子进程 PID
	
    .......
        
    p = copy_process(NULL, trace, NUMA_NO_NODE, args);//复制 task_struct及其他结构体
    add_latent_entropy();

    if (IS_ERR(p))
        return PTR_ERR(p);

    ......
        
    trace_sched_process_fork(current, p);//通知调度器新进程已创建
    
    pid = get_task_pid(p, PIDTYPE_PID);//获取新进程 pid 结构体
    nr = pid_vnr(pid);
    
    wake_up_new_task(p);//将新任务放入就绪队列等待调度器调度

     return nr;
}

该方法主要调用copy_process方法生成子进程的task_struct,接着调用wake_up_new_task方法将新任务放入就绪队列等待调度器调度。其主要代码如下:

/**
 * copy_process - 复制当前进程,创建一个新的进程(或线程)
 * @pid: 指向新进程的 PID 结构体
 * @trace: 跟踪标志
 * @node: NUMA 结点
 * @args: 进程克隆参数
 */
struct task_struct *copy_process(struct pid *pid, int trace, int node, struct kernel_clone_args *args)
{
	struct task_struct *p;  // 指向新创建进程的 task_struct
    const u64 clone_flags = args->flags;  // 进程克隆标志
	struct nsproxy *nsp = current->nsproxy;  // 继承当前进程的命名空间指针
    ......

    // 复制当前进程的 task_struct 结构体,为新进程分配内存
	p = dup_task_struct(current, node);
    
    // 复制文件描述符表,根据 clone_flags 选择共享或复制
    retval = copy_files(clone_flags, p, args->no_files);
	if (retval)
		goto bad_fork_cleanup_semundo;

    // 复制文件系统信息(当前工作目录、root 目录等)
	retval = copy_fs(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_files;

    // 复制信号处理程序(包括信号屏蔽字、处理函数)
	retval = copy_sighand(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_fs;

    // 复制信号队列和信号状态
	retval = copy_signal(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_sighand;

    // 复制虚拟内存(地址空间),如果 CLONE_VM 设置,则共享地址空间
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;

    // 复制命名空间(PID、网络、IPC、挂载等)
	retval = copy_namespaces(clone_flags, p);
    ......

    // 如果新进程不是 init 进程,则为其分配新的 PID
    if (pid != &init_struct_pid) {
		pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid, args->set_tid_size);
		if (IS_ERR(pid)) {  // 检查分配 PID 是否成功
			retval = PTR_ERR(pid);
			goto bad_fork_cleanup_thread;
		}
	}
    ......

    return p;  // 返回新进程的 task_struct 指针
}

该方法首先调用dup_task_struct函数复制了一个新的task_struct内核对象,接着对各种核心对象进行复制处理,处理命名空间下的pid申请。

2.1 复制task_struct结构体

copy_process使用dup_task_struct复制新的task_struct内核对象:

p = dup_task_struct(current, node);

其参数current表示当前进程,即父进程;node表示NUMA架构下的某个结点。这次复制只会复制task_struct结构体本身,内部的成员仅仅是复制了指针,仍然指向父进程指针指向的,如图所示。

在这里插入图片描述

源代码如下:

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
    struct task_struct *tsk;
    ......
    tsk = alloc_task_struct_node(node);//申请task_struct结构体对象
    if (!tsk)
        return NULL;

	......
    err = arch_dup_task_struct(tsk, orig);//复制 task_struct 结构
    if (err)
        goto free_tsk;
        
    err = alloc_thread_stack_node(tsk, node);//为新进程分配内核栈
    if (err)
        goto free_tsk;

	.......
}

2.2 复制files_struct

copy_process() 中,copy_files() 负责复制或共享 files_struct(文件描述符表),决定新进程如何管理文件描述符:

static int copy_files(unsigned long clone_flags, struct task_struct *tsk, int no_files)
{
    struct files_struct *oldf, *newf;    
    /* 获取当前进程(父进程)的文件描述符表 */
    oldf = current->files;    
    /* 如果当前进程没有打开任何文件(如某些后台进程),直接返回 */
    if (!oldf)
        return 0;
    /* 如果 no_files 置位,新进程不继承文件描述符,files 设为空 */
    if (no_files) {
        tsk->files = NULL;
        return 0;
    }
    /* 
     * 如果 clone_flags 设置了 CLONE_FILES,则新进程共享父进程的 files_struct,
     * 仅增加 files_struct 的引用计数,而不复制文件描述符表。
     * 这种情况通常用于多线程(pthread),所有线程共享相同的文件描述符。
     */
    if (clone_flags & CLONE_FILES) {
        atomic_inc(&oldf->count);
        return 0;
    }
    /* 
     * 如果没有设置 CLONE_FILES(默认行为,fork 也在此情况),
     * 需要为新进程创建一个独立的 files_struct,并复制父进程的文件描述符表。
     */
    newf = dup_fd(oldf, NULL);    
    /* 如果复制失败,返回错误码 */
    if (IS_ERR(newf))
        return PTR_ERR(newf);
    /* 绑定新进程的文件描述符表 */
    tsk->files = newf;
    return 0;
}

fork() 默认不传入 CLONE_FILES,因此新进程不会共享 files_struct,而是调用 dup_fd() 复制一份新的文件描述符表:

在这里插入图片描述

2.3 复制file_struct

copy_process() 中,copy_fs() 负责在 fork()clone() 时处理 文件系统相关信息,即 fs_struct。该函数决定新进程是共享还是复制父进程的文件系统信息,如当前工作目录、根目录等:

static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
    struct fs_struct *fs = current->fs;  // 获取当前进程(父进程)的 fs_struct

    /* 
     * 如果 clone_flags 设置了 CLONE_FS,则子进程共享父进程的 fs_struct,
     * 例如 `chdir()` 或 `chroot()` 会影响所有线程。
     */
    if (clone_flags & CLONE_FS) {
      .......
        /* 递增 fs_struct 的引用计数 */
        fs->users++;
        spin_unlock(&fs->lock);
        return 0; // 共享成功,直接返回
    }

    /* 
     * 如果没有设置 CLONE_FS(默认行为,fork 情况),则复制 fs_struct,
     * 使新进程拥有独立的 fs_struct。
     */
    tsk->fs = copy_fs_struct(fs);
    if (!tsk->fs)
        return -ENOMEM; // 复制失败,返回内存不足错误
    return 0;
}

fork() 默认不传入CLONE_FS,所以进入copy_fs_struct方法:

struct fs_struct *copy_fs_struct(struct fs_struct *old)
{
    struct fs_struct *fs = kmem_cache_alloc(fs_cachep, GFP_KERNEL);
    /* 如果分配失败,返回 NULL */
    if (fs) {
        fs->users = 1;   // 初始化用户计数,表示该 fs_struct 仅被一个进程使用
        fs->in_exec = 0; // 该进程未处于 exec() 过程中
        spin_lock_init(&fs->lock); // 初始化锁,确保并发访问安全
        seqcount_spinlock_init(&fs->seq, &fs->lock); // 初始化序列锁
        fs->umask = old->umask; // 复制 umask(文件权限掩码)
        spin_lock(&old->lock);  // 加锁,防止并发修改

        fs->root = old->root;   // 复制根目录路径
        path_get(&fs->root);    // 增加根目录引用计数,防止被释放

        fs->pwd = old->pwd;     // 复制当前工作目录路径
        path_get(&fs->pwd);     // 增加工作目录引用计数

        spin_unlock(&old->lock); // 释放锁
    }
}

使用了当前进程的值对新进程的umask掩码、pwd、root目录进行初始化:

在这里插入图片描述

2.4 复制mm_struct

copy_process() 中,copy_mm() 负责复制或共享进程的内存管理结构 mm_struct,即进程的虚拟地址空间

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
    struct mm_struct *mm, *oldmm;
    tsk->min_flt = tsk->maj_flt = 0;  // 次级/主缺页次数清零
    tsk->nvcsw = tsk->nivcsw = 0;     // 进程上下文切换计数清零
#ifdef CONFIG_DETECT_HUNG_TASK
    tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
    tsk->last_switch_time = 0;        // 记录进程最后切换时间
#endif
    tsk->mm = NULL;  //指向进程的虚拟地址空间
    tsk->active_mm = NULL;//用于内核线程,没有 mm,但仍可能访问用户地址空间
    oldmm = current->mm;
    if (!oldmm)
        return 0;
    if (clone_flags & CLONE_VM) {//如果 CLONE_VM 置位(如 pthread_create() 线程)
        mmget(oldmm);//增加 mm_struct 引用计数
        mm = oldmm;//共享 mm_struct,多个线程共享地址空间
    }
    else {//如果 CLONE_VM 未置位
        mm = dup_mm(tsk, current->mm);//调用 dup_mm() 复制 mm_struct,子进程拥有独立地址空间
        if (!mm)
            return -ENOMEM;
    }
    tsk->mm = mm;//使子进程拥有虚拟地址空间
    tsk->active_mm = mm;//进程访问的活动地址空间
    sched_mm_cid_fork(tsk);
    return 0;
}

在进程创建时,CLONE_VM未置位,所以会调用dup_mm申请一个新的地址空间:

static struct mm_struct *dup_mm(struct task_struct *tsk,
				struct mm_struct *oldmm)
{
	struct mm_struct *mm;
	int err;

	mm = allocate_mm();  // 分配 mm_struct 结构体
	if (!mm)
		goto fail_nomem;  // 如果分配失败,跳转到错误处理

	memcpy(mm, oldmm, sizeof(*mm));  // 复制 oldmm 的内容到新 mm_struct

..........
}

在该方法中,通过allocate_mm申请了新的mm_struct,并且将当前进程的地址空间复制到新的mm_struct对象进行了初始化。故新进程的虚拟地址空间和当前进程的是一样的,所以父进程的虚拟地址空间中的数据,子进程都可以直接使用。

在这里插入图片描述

2.5 复制nsproxy

关于进程创建部分的nsproxy复制及pid的申请在该博文中详细分析过:

Linux资源隔离基础(二):namespace-CSDN博客

2.6 进入就绪队列

当copy_process方法执行完毕,新进程的task_struct对象就创建出来了,接下来内核会调用wake_up_new_task方法将该新进程加入就绪队列,等待调度。

void wake_up_new_task(struct task_struct *p)
{
    struct rq_flags rf;  // 记录运行队列锁标志
    struct rq *rq;       // 运行队列指针
    int wake_flags = WF_FORK;  // 进程创建时的 wake flag
    raw_spin_lock_irqsave(&p->pi_lock, rf.flags); // 加锁,防止并发访问
    WRITE_ONCE(p->__state, TASK_RUNNING);//新进程的状态改为 TASK_RUNNING,表示可调度
    #ifdef CONFIG_SMP
    /*
     * Fork 负载均衡(SMP 多核负载均衡)
     */
    p->recent_used_cpu = task_cpu(p);  // 记录最近使用的 CPU
    rseq_migrate(p);  // 处理 rseq 迁移(用于线程安全的 restartable sequences)
    __set_task_cpu(p, select_task_rq(p, task_cpu(p), &wake_flags));//直接设置 task_struct->cpu,避免不必要的 sched_class::migrate_task_rq 调用。
#endif
    rq = __task_rq_lock(p, &rf);  // 锁定进程的运行队列
    update_rq_clock(rq);  // 更新运行队列时钟
    post_init_entity_util_avg(p);  // 初始化负载追踪数据(CFS调度)
    activate_task(rq, p, ENQUEUE_NOCLOCK | ENQUEUE_INITIAL);//将新进程 p 加入 rq
    trace_sched_wakeup_new(p);  // 记录 wakeup 事件(用于性能分析)
    wakeup_preempt(rq, p, wake_flags);//触发调度器检查新进程 p 是否应该抢占当前运行的任务
#ifdef CONFIG_SMP
    if (p->sched_class->task_woken) {
        rq_unpin_lock(rq, &rf);  // 释放 `rq` 锁
        p->sched_class->task_woken(rq, p);  // 运行 `task_woken` 钩子
        rq_repin_lock(rq, &rf);  // 重新锁定 `rq`
    }
#endif
    task_rq_unlock(rq, p, &rf);
}

关于调度的部分,将在后续章节分析。【挖个坑】

3.线程的创建

在上一章节中,我们深入分析了 fork() 机制,探讨了 Linux 进程的创建过程,包括 task_struct 复制、以及 wake_up_new_task() 进程调度等关键步骤。进程创建虽然能提供独立的资源隔离,但进程间通信和资源共享的开销较大,因此,在多线程并发环境下,线程(Thread) 成为更高效的执行单元。

Linux 内核通过 clone() 系统调用实现线程创建,glibc 封装了 clone(),并提供 pthread_create() 作为标准 API,供用户态程序使用。Linux 上的大多数用户态程序都依赖 glibc 来调用内核功能,它是用户程序和内核之间的桥梁。

在很多应用程序中,线程的创建由 pthread_create() 负责,而 glibc 通过 clone() 实现线程管理:

在这里插入图片描述

接下来深入glibc源码和linux内核源码分析线程的创建过程,首先是**__pthread_create_2_1方法,该方法调用create_thread**方法创建线程:

static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
			  bool *stopped_start, void *stackaddr,
			  size_t stacksize, bool *thread_ran){
....
    
  const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
			   | CLONE_SIGHAND | CLONE_THREAD
			   | CLONE_SETTLS | CLONE_PARENT_SETTID
			   | CLONE_CHILD_CLEARTID
			   | 0);//构造 clone() 需要的 flags
  TLS_DEFINE_INIT_TP (tp, pd);

  struct clone_args args =
    {
      .flags = clone_flags,
      .pidfd = (uintptr_t) &pd->tid,
      .parent_tid = (uintptr_t) &pd->tid,
      .child_tid = (uintptr_t) &pd->tid,
      .stack = (uintptr_t) stackaddr,
      .stack_size = stacksize,
      .tls = (uintptr_t) tp,
    };//组装 clone_args 结构
  int ret = __clone_internal (&args, &start_thread, pd);//调用 __clone_internal() 进入 clone3() 系统调用,创建新线程。
  if (__glibc_unlikely (ret == -1))
    return errno;
}

这里调用了clone系统调用,clone和fork一样,最后都会执行kernel_clone方法,只是传入的参数不同。传入了CLONE_VM、CLONE_FS、CLONE_FILES等标志,这意味着,新线程与原线程或进程共用一套mm_struct、fs_struct、files_struct。

线程创建中的不同

与上述新建进程时每个新结构体都要新建不同,新建线程时每一个结构体都与创建它的进程或线程是共用一套的,这也就体现了进程和线程的本质区别:即进程有独立的虚拟地址空间,线程是与别的任务共用。最后创建结束后就是这样:

在这里插入图片描述

总结

在本系列博文中,我们从 Linux 内核源码与 glibc 线程库 的角度,深入剖析了 进程和线程的创建过程,并围绕fork()、clone() 的区别展开了详细讨论。

由上分析,进程的创建调用了fork系统调用,而线程的创建调用clone系统调用,在此也可以总结一下fork和clone的区别:

对比项fork()clone()
资源共享不共享,采用写时复制(COW)允许选择性共享资源(CLONE_*
进程 IDpid,不同于父进程CLONE_THREAD相同 tgid(线程) 未设置 CLONE_THREAD不同 pid(进程)
地址空间复制 mm_struct(COW)CLONE_VM共享 mm_struct(线程)
文件描述符复制 files_structCLONE_FILES共享 files_struct
信号处理复制 sighand_structCLONE_SIGHAND共享 sighand_struct
性能最慢(COW 复制内存)fork()(可共享资源)
适用场景创建独立进程创建线程

欢乐的时光总是短暂的,我们下期再见!

相关文章:

  • Python 网络编程实战:5分钟实现多线程下载工具与 Web 服务器
  • Android SharedPreference 详解
  • 深入理解Linux进程管理:从基础到高级操作指南
  • GPU算力哪家好?GpuGeek推出高性能GPU云服务
  • 泛型、泛型上限、泛型下限、泛型通配符
  • java虚拟机(JVM)以及各种参数详解
  • 力扣-哈希表-844 比较含退格的字符串
  • 理解C语言中的extern关键字
  • 【机器人-基础知识】欧拉角、旋转矩阵和四元数
  • python爬虫碰到IP被封的情况,如何解决?
  • 【Rust并发编程深度解析:内存模型与异步运行时实现原理】
  • JavaScript API与WebRTC技术解析:EasyRTC嵌入式视频通话SDK的实现
  • vue3:密码加密解密实现
  • pdf合并工具
  • OpenGL(4)着色器
  • LearnOpenGL-笔记-其三
  • 文件跨国传输如何加速?UDP改造机制解析
  • vscode出现:No module named ‘requests‘ 问题的解决方法
  • 【AI】单台10卡4090 openEuler服务器离线部署kasm workspace 提供简单的GPU云服务 虚拟化桌面
  • 每天一篇《目标检测》文献(一)
  • 李铁案二审驳回上诉,维持一审有期徒刑20年的判决
  • 中信银行一季度净利195.09亿增1.66%,不良率持平
  • 李在明涉嫌违反《公职选举法》案将于5月1日宣判
  • 王毅:为改革完善全球治理作出金砖贡献
  • “不意外”和“不遗余力”,直击上海商超对接外贸企业
  • 杭州银行一季度净赚超60亿增逾17%,增速较去年同期有所回落