文章

linux内核笔记(二)进程的表示和相关系统调用

linux内核笔记(二)进程的表示和相关系统调用

进程的表示

进程的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct task_struct {
  volatile long state; // 进程状态
  void *stack; // 线程栈地址指针 void * 万能指针
  ...
  /* 是历史遗留的 int 类型接口,方便使用 */
  pid_t pid;  //进程pid 
  pid_t tgid; // 线程组id
  ...
  struct task_struct *group_leader; // 线程组长
  struct nsproxy *nsproxy;          // 命名空间
  ...
  /* 信息处理相关 线程组共享 */
  struct signal_struct  *signal;          // 信号状态信息:共享信号位图、资源统计、组 ID
  struct sighand_struct __rcu   *sighand; // 信号的处理动作
  sigset_t      blocked;                  // 信号阻塞信息
  sigset_t      real_blocked;             // 在调用信号处理程序时的blocked的备份
  ...
  struct pid     *thread_pid;       // pid关联
  struct hlist_node pid_links[PIDTYPE_MAX];  // 不同pid类型的关联 (例如进程组、线程组)
  struct list_head children;     // 子进程链表
  struct list_head sibling;      // 连接到父进程的子进程链表
  ...
  struct thread_struct thread;   // 特定架构cpu的状态信息,包含寄存器等数据
  ...
  // 通过此宏将stack万能指针 指向thread_info中的栈
  #define task_thread_info(task)((struct thread_info *)(task)->stack)
}

进程的状态

Thread Status 进程状态流转

  • 运行 :该进程此刻正在执行。

  • 等待 :进程能够运行,但没有得到许可,因为CPU分配给另一个进程。调度器可以在下一次任务切换时选择该进程。

  • 睡眠 :进程正在睡眠无法运行,因为它在等待一个外部事件。调度器无法在下一次任务切换时选择该进程。

进程与命名空间的关联

Linux 命名空间是实现容器技术(如 Docker、LXC)和沙箱隔离的关键基础。它提供了一种机制,使得进程可以拥有自己独立的系统资源视图,而这些视图与其他进程的视图是隔离的。

命名空间层次关联 命名空间层次关联

进程与命名空间的关联 进程与命名空间的关联

命名空间与进程关联的数据结构-nsproxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct nsproxy {
  refcount_t count;   // 引用计数
  // Unix Time-sharing System 控制主机名和域名的隔离。
  // 通过 UTS 命名空间,不同的容器或进程可以感知自己的主机名和域名,从而实现隔离和独立运行。
  struct uts_namespace *uts_ns;
  struct ipc_namespace *ipc_ns;   // 进程间通信(IPC) ns视图。
  struct mnt_namespace *mnt_ns;   // 文件系统的ns视图
  struct pid_namespace *pid_ns;   // 进程ID的ns视图
  struct net *net_ns;             // 网络命名空间视图
  struct time_namespace *time_ns; // 时钟命名空间视图
  struct time_namespace *time_ns_for_children;  //  子进程的时钟命名空间视图
  struct cgroup_namespace *cgroup_ns; // cgroup命名空间视图
}; 

内核命名空间通用结构体

1
2
3
4
5
6
struct ns_common {
  struct dentry *stashed; // 扩展的dentry信息
  const struct proc_ns_operations *ops; // 命名空间操作函数指针
  unsigned int inum;      // 命名空间的inode编号
  refcount_t count;       // 引用计数
};

uts命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct uts_namespace {
  struct new_utsname name;        // 存储主机名、域名等UTS信息
  struct user_namespace *user_ns; // 关联的用户命名空间(用于权限控制)
  struct ucounts *ucounts;        // 用户资源计数(用于容器限制)
  struct ns_common ns;            // 内核命名空间通用结构体 包含了引用计数
}
#define __NEW_UTS_LEN 64
struct new_utsname {
  char sysname[__NEW_UTS_LEN + 1];   // 操作系统名称(如 "Linux")
  char nodename[__NEW_UTS_LEN + 1];  // 主机名(通过 `hostname` 命令查看)
  char release[__NEW_UTS_LEN + 1];   // 内核版本
  char version[__NEW_UTS_LEN + 1];   // 内核编译版本信息
  char machine[__NEW_UTS_LEN + 1];   // 硬件架构(如 "x86_64")
  char domainname[__NEW_UTS_LEN + 1];// 域名(NIS域名)
};

用户命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct user_namespace {
  struct uid_gid_map  uid_map;    // UID 映射表
  struct uid_gid_map  gid_map;    // GID 映射表
  struct uid_gid_map  projid_map; // Project ID 映射表
  struct user_namespace *parent;  // 父用户命名空间的引用
  int    level;                 // 命名空间嵌套级别
  kuid_t    owner;              // 命名空间的创建者 UID
  kgid_t    group;              // 命名空间的创建者 GID
  struct ns_common  ns;         // 内核命名空间通用结构体
  unsigned long  flags;         // 命名空间标
  struct ucounts    *ucounts;   // 用户计数器
}; 

// union 结合体 uid_gid_map的数据较少时,使用数组方便管理;数量较多时使用链表管理;
struct uid_gid_map { /* 64 bytes -- 1 cache line */
  union {
    struct {
      struct uid_gid_extent extent[UID_GID_MAP_MAX_BASE_EXTENTS];
      u32 nr_extents;
    };
    struct {
      struct uid_gid_extent *forward;
      struct uid_gid_extent *reverse;
    };
  };
};

struct uid_gid_extent {
  u32 first;        // 命名空间内的起始 ID
  u32 lower_first;  // 父命名空间或宿主系统中的起始 ID
  u32 count;        // 映射的 ID 数量
};
/* e.g. {first=0, lower_first=100000, count=1000}
 * 命名空间内的 UID 0 映射到宿主系统的 UID 100000,UID 1 映射到 100001
 * 以此类推
 */

PID命名空间数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct pid_namespace {
  struct idr idr;                 //(ID Allocator)数据结构,用于管理 PID 分配
  struct rcu_head rcu;            // RCU(Read-Copy-Update)头,用于延迟释放命名空间
  unsigned int pid_allocated;     // 已分配的 PID 数量
  struct task_struct *child_reaper;// 负责处理子进程的信号(如 SIGCHLD)和清理僵尸进程。
  struct kmem_cache *pid_cachep;  // 指向分配 struct pid 的 slab 缓存。
  unsigned int level;             // 表示 PID 命名空间的嵌套深度
  struct pid_namespace *parent;   // 父命名空间
  struct user_namespace *user_ns; // 关联的用户命名空间
  struct ucounts *ucounts;        // 用户计数器
  // 当命名空间内的 child_reaper(PID 1)退出时,可能触发命名空间“重启”,影响子进程的行为。
  int reboot;
  struct ns_common ns;
} 

进程ID

UNIX进程总是会分配一个号码用于在其命名空间中唯一地标识它们。该号码被称作进程ID号 (Process ID),简称PID。用fork 或clone 产生的每个进程都由内核自动地分配了一个新的唯一的PID值。

但每个进程除了PID这个特征值之外,还有其他的ID。有下列几种可能的类型。

  • 处于某个线程组(在一个进程中,以标志CLONE_THREAD来调用clone建立的该进程的不同的执行上下文)中的所有进程都有统一的线程组ID (TGID)。如果进程没有使用线程,则其PID和TGID相同。线程组中的主进程被称作组长 (group leader)。通过clone创建的所有线程的task_structgroup_leader成员,会指向组长的task_struct实例
  • 独立进程可以合并成进程组 (使用setpgrp 系统调用)。进程组成员的task_struct 的pgrp 属性值都是相同的,即进程组组长的PID。

进程ID与进程、命名空间的关联 进程ID与进程、命名空间的关联

进程ID的结构与辅助函数

pid数据结构与辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* 表示特定命名空间的进程PID */
struct upid {
  int nr;  // PID的数值
  struct pid_namespace *ns; // 指向该id所属命名空间的指针
};

struct pid
{
  refcount_t count;       // 引用计数
  unsigned int level;     // PID 命名空间嵌套级别
  spinlock_t lock;        // 自旋锁,保护 PID 数据
  struct dentry *stashed; // 扩展的dentry信息
  struct hlist_head tasks[PIDTYPE_MAX]; // 关联的进程列表
  ...
  struct rcu_head rcu;    // RCU 延迟释放头
  struct upid numbers[];  // 灵活数组,存储多级PID信息 长度是level+1
};

/* 获取进程pid核心函数声明 */
pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type, struct pid_namespace *ns);

/*
 * 几个从不同命名空间取task_struct的不同id的辅助内联函数
 * task_xid_nr()     : 全局ID, i.e. 从init namespace取的ID;
 * task_xid_vnr()    : 虚拟ID, i.e. 从当前的 pid namespace 取的ID;
 * task_xid_nr_ns()  : 获取指定namespace的id
 */
static inline pid_t task_pid_nr(struct task_struct *tsk)
{
  return tsk->pid;
}

static inline pid_t task_pid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
{
  return __task_pid_nr_ns(tsk, PIDTYPE_PID, ns);
}

static inline pid_t task_pid_vnr(struct task_struct *tsk)
{
  return __task_pid_nr_ns(tsk, PIDTYPE_PID, NULL);
}


static inline pid_t task_tgid_nr(struct task_struct *tsk)
{
  return tsk->tgid;
}

/*
 * 几个从不同命名空间取pid结构的id值的辅助内联函数
 * pid_nr()    : 全局ID, i.e. 从init namespace取的ID;
 * pid_vnr()   : 虚拟ID, i.e. 从当前的 pid namespace 取的ID;
 * pid_nr_ns() : 获取指定namespace的id
 */
static inline pid_t pid_nr(struct pid *pid)
{
  pid_t nr = 0;
  if (pid)
    nr = pid->numbers[0].nr;
  return nr;
}

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns);
pid_t pid_vnr(struct pid *pid);

static inline struct pid *task_pid(struct task_struct *task)
{
  return task->thread_pid;
}

/* 从进程 ID(PID)结构中获取其所属的 PID 命名空间(PID Namespace)指针 */
static inline struct pid_namespace *ns_of_pid(struct pid *pid)
{
  struct pid_namespace *ns = NULL;
  if (pid)
    ns = pid->numbers[pid->level].ns;
  return ns;
}


不同的pid_type

1
2
3
4
5
6
7
8
enum pid_type 
{ 
  PIDTYPE_PID, 
  PIDTYPE_TGID,  // 线程组id
  PIDTYPE_PGID,  // 进程组id 
  PIDTYPE_SID,   // 会话id 
  PIDTYPE_MAX 
}; 

核心辅助函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/* 根据pid的数值查找pid结构 通过idr中的xarray */
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
  return idr_find(&ns->idr, nr);
}
EXPORT_SYMBOL_GPL(find_pid_ns);

struct pid *find_vpid(int nr)
{
  return find_pid_ns(nr, task_active_pid_ns(current));
}
EXPORT_SYMBOL_GPL(find_vpid);

/* 
 * 获取pid关联的task_struct 通过rcu保护的hlist来实现
 * task_struct->pid_links   <===>   pid->tasks
 */
struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
  struct task_struct *result = NULL;
  if (pid) {
    struct hlist_node *first;
    first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
                lockdep_tasklist_lock_is_held());
    if (first)
      result = hlist_entry(first, struct task_struct, pid_links[(type)]);
  }
  return result;
}
EXPORT_SYMBOL(pid_task);


/* 获取pid结构在指定ns中的pid数值 */
pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
  struct upid *upid;
  pid_t nr = 0;

  if (pid && ns->level <= pid->level) {
    upid = &pid->numbers[ns->level];
    if (upid->ns == ns) // double check 确保ns一致
      nr = upid->nr;
  }
  return nr;
}
EXPORT_SYMBOL_GPL(pid_nr_ns);
....
pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
      struct pid_namespace *ns)
{
  pid_t nr = 0;
  rcu_read_lock();
  if (!ns)
    ns = task_active_pid_ns(current);
  nr = pid_nr_ns(rcu_dereference(*task_pid_ptr(task, type)), ns);
  rcu_read_unlock();

  return nr;
}
EXPORT_SYMBOL(__task_pid_nr_ns);

/* 获取tsk当前活跃的ns */
struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
{
  return ns_of_pid(task_pid(tsk));
}
EXPORT_SYMBOL_GPL(task_active_pid_ns);

...

/* 获取task对应pid_type的pid */
static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type)
{
  return (type == PIDTYPE_PID) ?
    &task->thread_pid :
    &task->signal->pids[type];
}

/* 将pid实例与task_struct绑定 必须持有 tasklist_lock 的写锁 */
void attach_pid(struct task_struct *task, enum pid_type type)
{
  struct pid *pid = *task_pid_ptr(task, type);
  hlist_add_head_rcu(&task->pid_links[type], &pid->tasks[type]);
}

分配pid实例

idr分配器

IDR 分配器是一个基于xarray 包装的整数 ID 管理机制,设计目标是高效分配和回收整数 ID,同时支持动态范围的 ID 分配。

1
2
3
4
5
struct idr {
  struct radix_tree_root  idr_rt;
  unsigned int    idr_base;       // ID 分配的起始基数(通常为 0 或 1)
  unsigned int    idr_next;       // 下一个分配的 ID
};

内核4.20之后 用xarray取代了radix_tree

1
#define radix_tree_root    xarray 
Xarray 数据结构

Xarray 的核心是一个灵活的数组,通过 index 索引 entry (默认为 NULL)。它维护着 Index 到 entry 的映射关系,类似于一个 Radix Tree。

Xarray 中的 entry 存储单位称为 Slot,可存放特定范围的整数值、字节对齐的指针或 NULL。Xarray 会利用 entry 指针地址的末尾比特位存储类型信息,以便区分是数值、指针还是空值,并支持 Tag 等特性

由于指针是经过内存对齐的(任何从kmalloc()和 alloc_page()返回的指针来说都是如此),所以低位的比特都为0 可以用这些位存储entry类型信息

xarray xarray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#ifndef XA_CHUNK_SHIFT
/* 每个node下slot数量的位移值   */
#define XA_CHUNK_SHIFT  (IS_ENABLED(CONFIG_BASE_SMALL) ? 4 : 6)
#endif
/* 每个node下slot数量 通过上面的位移值计算 默认是6的2^6 = 64个 如果小设备 可以配置为2^4 =16个 */
#define XA_CHUNK_SIZE   (1UL << XA_CHUNK_SHIFT)
/* 掩码 位运算offset使用 例如通过 index >> shift & XA_CHUNK_MASK 可快速计算某个层级node上的index对应的offset */
#define XA_CHUNK_MASK   (XA_CHUNK_SIZE - 1)


struct xarray {
  spinlock_t  xa_lock;  // 自旋锁,保护并发访问
  gfp_t   xa_flags;     // 内存分配标志
  void __rcu *  xa_head;// 指向根节点的 RCU 指针
};

struct xa_node {
  unsigned char shift;    // 节点层级的位移(决定索引范围)
  unsigned char offset;   // 在父节点中的偏移量
  unsigned char count;    // 非空槽位数
  unsigned char nr_values;  // 值(非指针)槽位数
  struct xa_node __rcu *parent; // 父节点
  struct xarray *array;   // 所属 XArray
  void __rcu  *slots[XA_CHUNK_SIZE]; // 槽位数组,存储子节点或数据 64个
  ...
};

struct xa_state {
  struct xarray *xa;      // 目标 XArray
  unsigned long xa_index; // 当前操作的索引
  unsigned char xa_shift; // 当前节点的位移
  unsigned char xa_offset;// 当前槽位偏移
  unsigned char xa_pad;   // 编译器内存对齐填充
  struct xa_node *xa_node;// 当前节点
  ...
};

void *xa_load(struct xarray *, unsigned long index);
void *xa_store(struct xarray *, unsigned long index, void *entry, gfp_t);
void *xa_erase(struct xarray *, unsigned long index);
void *xa_store_range(struct xarray *, unsigned long first, unsigned long last,
      void *entry, gfp_t);

分配pid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* 
 * 核心函数: 为task_struct绑定pid实例  ns: 命名空间 set_tid: 指定的pid数组  
 * set_tid_size: pid数组的长度
 */
struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
          size_t set_tid_size)
{
  struct pid *pid;
  enum pid_type type;
  int i, nr;
  struct pid_namespace *tmp;
  struct upid *upid;
  int retval = -ENOMEM;

  if (set_tid_size > ns->level + 1)
    return ERR_PTR(-EINVAL);
  // 申请pid实例内存
  pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
  if (!pid)
    return ERR_PTR(retval);

  tmp = ns;
  pid->level = ns->level; //继承ns的level

  // 从当前层级ns开始,向上层级遍历 分配各层ns下的pid
  for (i = ns->level; i >= 0; i--) { 
    int tid = 0;

    if (set_tid_size) { // 如果指定了pid数组
      tid = set_tid[ns->level - i];

      retval = -EINVAL;
      if (tid < 1 || tid >= pid_max)
        goto out_free;
      // 如果tid不是1 而且没有命名空间的僵尸进程清理器 也报错
      if (tid != 1 && !tmp->child_reaper)
        goto out_free;
      retval = -EPERM;
      if (!checkpoint_restore_ns_capable(tmp->user_ns))
        goto out_free;
      set_tid_size--;
    }

    idr_preload(GFP_KERNEL);
    spin_lock_irq(&pidmap_lock);  // pid位图加锁

    if (tid) {
      // 使用idr分配器 分配指定的pid
      nr = idr_alloc(&tmp->idr, NULL, tid,
                tid + 1, GFP_ATOMIC);
      if (nr == -ENOSPC)
        nr = -EEXIST;
    } else {
      int pid_min = 1;
      if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
        pid_min = RESERVED_PIDS;

      // 使用idr分配器 自动分配pid
      nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
                pid_max, GFP_ATOMIC);
    }
    spin_unlock_irq(&pidmap_lock); // pid位图解锁
    idr_preload_end();

    if (nr < 0) {
      retval = (nr == -ENOSPC) ? -EAGAIN : nr;
      goto out_free;
    }

    pid->numbers[i].nr = nr;
    pid->numbers[i].ns = tmp;
    tmp = tmp->parent;
  }

  retval = -ENOMEM;

  get_pid_ns(ns);
  refcount_set(&pid->count, 1);
  spin_lock_init(&pid->lock);
  for (type = 0; type < PIDTYPE_MAX; ++type)
    INIT_HLIST_HEAD(&pid->tasks[type]);

  init_waitqueue_head(&pid->wait_pidfd);
  INIT_HLIST_HEAD(&pid->inodes);
  // 从灵活数组中 定位当前命名空间(ns)中对应的 upid 结构 
  upid = pid->numbers + ns->level;
  spin_lock_irq(&pidmap_lock);
  if (!(ns->pid_allocated & PIDNS_ADDING))
    goto out_unlock;
  pid->stashed = NULL;
  pid->ino = ++pidfs_ino;
  for ( ; upid >= pid->numbers; --upid) {
    /* 
      * 通过 idr_replace() 将 PID 插入到对应命名空间的 IDR 树中
      * 增加命名空间的 PID 分配计数
      */
    idr_replace(&upid->ns->idr, pid, upid->nr);
    upid->ns->pid_allocated++;
  }
  spin_unlock_irq(&pidmap_lock);

  return pid;

  out_unlock:
  spin_unlock_irq(&pidmap_lock);
  put_pid_ns(ns);

  out_free: // 释放被分配的内存、pid 报错
  spin_lock_irq(&pidmap_lock);
  while (++i <= ns->level) {
    upid = pid->numbers + i;
    idr_remove(&upid->ns->idr, upid->nr);
  }

  /* On failure to allocate the first pid, reset the state */
  if (ns->pid_allocated == PIDNS_ADDING)
    idr_set_cursor(&ns->idr, 0);

  spin_unlock_irq(&pidmap_lock);

  kmem_cache_free(ns->pid_cachep, pid);
  return ERR_PTR(retval);
}

进程相关系统调用

进程复制

Linux实现了3个进程复制的系统调用。

  1. fork 建立父进程的一个完整副本,然后作为子进程执行。为减少与该调用相关的工作量,Linux使用了写时复制 (copy-on-write)技术。
  2. vfork 类似于fork ,但并不创建父进程数据的副本。相反,父子进程之间共享数据。这节省了大量CPU时间(如果一个进程操纵共享数据,则另一个会自动注意到)。vfork 设计用于子进程形成后立即执行execve 系统调用加载新程序的情形。在子进程退出或开始新程序之前,内核保证父进程处于堵塞状态。
  3. clone 产生线程,可以对父子进程之间的共享、复制进行精确控制。

fork 、vfork 和clone 系统调用的入口点分别是sys_fork 、sys_vfork 和sys_clone 函数。 其内部均通过 kernel_clone_args 结构体将逻辑委托给了 kernel_clone 函数。kernel_clone_args的flags代表进程clone标记,其类型枚举在/include/uapi/linux/sched.h文件,其中有资源共享标记类型、进程父子关系标记类型、进程父子关系标记类型、命令空间隔阂了标记类型等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* sys_fork */
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
  struct kernel_clone_args args = {
    .exit_signal = SIGCHLD,
  };
  return kernel_clone(&args);
#else
  /* can not support in nommu mode */
  return -EINVAL;
#endif
}
#endif

/* sys_vfork */
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
  struct kernel_clone_args args = {
    .flags        = CLONE_VFORK | CLONE_VM,
    .exit_signal  = SIGCHLD,
  };
  return kernel_clone(&args);
}
#endif

/* sys_clone 为了兼容历史与诸多架构,参数顺序、数量会有一些调整 */
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS           // 最常见,也是现代 glibc 默认调用方式
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
      int __user *, parent_tidptr,
      unsigned long, tls,
      int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)  // 历史原因导致参数顺序颠倒
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
      int __user *, parent_tidptr,
      int __user *, child_tidptr,
      unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)  // 多了个 stack_size 参数
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
      int, stack_size,
      int __user *, parent_tidptr,
      int __user *, child_tidptr,
      unsigned long, tls)
#else                                   // 默认
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
      int __user *, parent_tidptr,
      int __user *, child_tidptr,
      unsigned long, tls)
#endif
{
  struct kernel_clone_args args = {
    /*
     * 标记位:进程/线程创建行为的位掩码,排除信号部分(低 8 位)。
     * 对应 clone() 中的 clone_flags & ~0xff,用于指定资源共享(如 CLONE_VM 共享内存)和隔离(如 CLONE_NEWPID 新 PID 命名空间)。
     * 无效组合(如 CLONE_SIGHAND 缺少 CLONE_VM)会返回 -EINVAL。
     */
    .flags        = (lower_32_bits(clone_flags) & ~CSIGNAL),
    .pidfd        = parent_tidptr,                            // PID 文件描述符存储位置
    .child_tid    = child_tidptr,                             // 指向子进程的TID指针
    .parent_tid   = parent_tidptr,                            // 指向父子进程的TID指针
    /*
     * 子进程退出信号:指定子进程终止时发送给父进程的信号编号(对应 clone() flags 的低 8 位)。
     * 默认 SIGCHLD;若为 0,则不发送信号。非 SIGCHLD 时,父进程需用 __WALL 或 __WCLONE 等待(wait(2))。
     */
    .exit_signal  = (lower_32_bits(clone_flags) & CSIGNAL),   
    .stack        = newsp,                                    // 子进程栈起始地址(最低字节指针)
    .tls          = tls,                                      // 线程本地存储  thread local storage
  };
  return kernel_clone(&args);
}
#endif

创建进程的核心逻辑栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
pid_t kernel_clone(struct kernel_clone_args *args)
{
  u64 clone_flags = args->flags;
  struct completion vfork;
  struct pid *pid;
  struct task_struct *p;
  int trace = 0;
  pid_t nr;

  /* 参数校验 在旧版 clone 中,parent_tid 可能被用作 pidfd 输出位置,这两者不能重叠。 */
  if ((clone_flags & CLONE_PIDFD) &&
      (clone_flags & CLONE_PARENT_SETTID) &&
      (args->pidfd == args->parent_tid))
    return -EINVAL;

  /* ptrace 跟踪调试相关 */
  if (!(clone_flags & CLONE_UNTRACED)) {
    if (clone_flags & CLONE_VFORK)
      trace = PTRACE_EVENT_VFORK;
    else if (args->exit_signal != SIGCHLD)
      trace = PTRACE_EVENT_CLONE;
    else
      trace = PTRACE_EVENT_FORK;

    if (likely(!ptrace_event_enabled(current, trace)))
      trace = 0;
  }
  /* 复制进程的核心函数*/
  p = copy_process(NULL, trace, NUMA_NO_NODE, args);
  add_latent_entropy(); // 只是收集熵源,用于增强系统随机性。可忽略

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

  /* 记录sched_process_fork 事件,用于性能追踪/分析。*/
  trace_sched_process_fork(current, p);
  /* 获取(task_struct)p的pid */
  pid = get_task_pid(p, PIDTYPE_PID);
  /* 获取(task_struct)p的虚拟命名空间下的pid */
  nr = pid_vnr(pid);

  /* 如果设置了 CLONE_PARENT_SETTID, 父进程会把子进程的 pid 写入指定的内存地址(用户态的 parent_tid)。*/
  if (clone_flags & CLONE_PARENT_SETTID)
    put_user(nr, args->parent_tid);
  /* 初始化vfork 同步机制的completion对象*/
  if (clone_flags & CLONE_VFORK) {
    p->vfork_done = &vfork;
    init_completion(&vfork);
    get_task_struct(p);
  }
  /* 如果启动了LRU_GEN 将子进程的内存加入多代LRU内存回收机制 */
  if (IS_ENABLED(CONFIG_LRU_GEN_WALKS_MMU) && !(clone_flags & CLONE_VM)) {
    /* lock the task to synchronize with memcg migration */
    task_lock(p);
    lru_gen_add_mm(p->mm);
    task_unlock(p);
  }
  /* 唤醒新进程 */
  wake_up_new_task(p);

  /* 调试事件通知 */
  if (unlikely(trace))
    ptrace_event_pid(trace, pid);
  /* 等待vfork完成(父进程阻塞) */
  if (clone_flags & CLONE_VFORK) {
    if (!wait_for_vfork_done(p, &vfork))
      ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
  }
  /* 释放 pid 引用计数,返回子进程的 PID。 */
  put_pid(pid);
  return nr;
}

copy_process是linux进程创建的核心函数,可以说是整个 fork() / vfork() / clone() 的灵魂。它是真正“复制进程描述符”的地方,它把当前进程的各种资源(文件、内存、信号、调度、命名空间等)复制或共享给子进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
__latent_entropy struct task_struct *copy_process(
          struct pid *pid,
          int trace,
          int node,
          struct kernel_clone_args *args)
{
  int pidfd = -1, retval;
  struct task_struct *p;
  struct multiprocess_signals delayed;
  struct file *pidfile = NULL;
  const u64 clone_flags = args->flags;
  struct nsproxy *nsp = current->nsproxy;

  /* 对标志做一些校验 避免出现非法组合 */
  if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
    return ERR_PTR(-EINVAL);

  if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
    return ERR_PTR(-EINVAL);

  if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
    return ERR_PTR(-EINVAL);

  if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
    return ERR_PTR(-EINVAL);

  if ((clone_flags & CLONE_PARENT) &&
        current->signal->flags & SIGNAL_UNKILLABLE)
    return ERR_PTR(-EINVAL);

  if (clone_flags & CLONE_THREAD) {
    if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
        (task_active_pid_ns(current) != nsp->pid_ns_for_children))
      return ERR_PTR(-EINVAL);
  }

  if (clone_flags & CLONE_PIDFD) {
    if (clone_flags & CLONE_DETACHED)
      return ERR_PTR(-EINVAL);
  }

  /* 初始化子进程的信号结构  如果不是clone模式 将子进程信号添加到父进程的multiprocess中 */
  sigemptyset(&delayed.signal);
  INIT_HLIST_NODE(&delayed.node);

  spin_lock_irq(&current->sighand->siglock);
  if (!(clone_flags & CLONE_THREAD))
    hlist_add_head(&delayed.node, &current->signal->multiprocess);
  recalc_sigpending();
  spin_unlock_irq(&current->sighand->siglock);
  retval = -ERESTARTNOINTR;
  if (task_sigpending(current))
    goto fork_out;

  retval = -ENOMEM;

  /* 复制父进程的所有字段、初始化状态为未运行,这一步之后新进程就在内存中了,但是没有pid、没有进入调度 */
  p = dup_task_struct(current, node);
  if (!p)
    goto fork_out;
  p->flags &= ~PF_KTHREAD;
  if (args->kthread)
    p->flags |= PF_KTHREAD;
  if (args->user_worker) {
    /*
      * Mark us a user worker, and block any signal that isn't
      * fatal or STOP
      */
    p->flags |= PF_USER_WORKER;
    siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP));
  }
  if (args->io_thread)
    p->flags |= PF_IO_WORKER;

	if (args->name)
		strscpy_pad(p->comm, args->name, sizeof(p->comm));

	p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? args->child_tid : NULL;
	/*
	 * Clear TID on mm_release()?
	 */
	p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? args->child_tid : NULL;

	ftrace_graph_init_task(p);

	rt_mutex_init_task(p);

	lockdep_assert_irqs_enabled();
#ifdef CONFIG_PROVE_LOCKING
	DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif
	retval = copy_creds(p, clone_flags);
	if (retval < 0)
		goto bad_fork_free;

	retval = -EAGAIN;
	if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
		if (p->real_cred->user != INIT_USER &&
		    !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
			goto bad_fork_cleanup_count;
	}
	current->flags &= ~PF_NPROC_EXCEEDED;

	/*
	 * If multiple threads are within copy_process(), then this check
	 * triggers too late. This doesn't hurt, the check is only there
	 * to stop root fork bombs.
	 */
	retval = -EAGAIN;
	if (data_race(nr_threads >= max_threads))
		goto bad_fork_cleanup_count;

	delayacct_tsk_init(p);	/* Must remain after dup_task_struct() */
	p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY);
	p->flags |= PF_FORKNOEXEC;
	INIT_LIST_HEAD(&p->children);
	INIT_LIST_HEAD(&p->sibling);
	rcu_copy_process(p);
	p->vfork_done = NULL;
	spin_lock_init(&p->alloc_lock);

	init_sigpending(&p->pending);

	p->utime = p->stime = p->gtime = 0;
#ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME
	p->utimescaled = p->stimescaled = 0;
#endif
	prev_cputime_init(&p->prev_cputime);

#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
	seqcount_init(&p->vtime.seqcount);
	p->vtime.starttime = 0;
	p->vtime.state = VTIME_INACTIVE;
#endif

#ifdef CONFIG_IO_URING
	p->io_uring = NULL;
#endif

	p->default_timer_slack_ns = current->timer_slack_ns;

#ifdef CONFIG_PSI
	p->psi_flags = 0;
#endif

	task_io_accounting_init(&p->ioac);
	acct_clear_integrals(p);

	posix_cputimers_init(&p->posix_cputimers);
	tick_dep_init_task(p);

	p->io_context = NULL;
	audit_set_context(p, NULL);
	cgroup_fork(p);
	if (args->kthread) {
		if (!set_kthread_struct(p))
			goto bad_fork_cleanup_delayacct;
	}
#ifdef CONFIG_NUMA
	p->mempolicy = mpol_dup(p->mempolicy);
	if (IS_ERR(p->mempolicy)) {
		retval = PTR_ERR(p->mempolicy);
		p->mempolicy = NULL;
		goto bad_fork_cleanup_delayacct;
	}
#endif
#ifdef CONFIG_CPUSETS
	p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
	seqcount_spinlock_init(&p->mems_allowed_seq, &p->alloc_lock);
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	memset(&p->irqtrace, 0, sizeof(p->irqtrace));
	p->irqtrace.hardirq_disable_ip	= _THIS_IP_;
	p->irqtrace.softirq_enable_ip	= _THIS_IP_;
	p->softirqs_enabled		= 1;
	p->softirq_context		= 0;
#endif

	p->pagefault_disabled = 0;

#ifdef CONFIG_LOCKDEP
	lockdep_init_task(p);
#endif

	p->blocked_on = NULL; /* not blocked yet */

#ifdef CONFIG_BCACHE
	p->sequential_io	= 0;
	p->sequential_io_avg	= 0;
#endif
#ifdef CONFIG_BPF_SYSCALL
	RCU_INIT_POINTER(p->bpf_storage, NULL);
	p->bpf_ctx = NULL;
#endif

	unwind_task_init(p);

	/* Perform scheduler related setup. Assign this task to a CPU. */
	retval = sched_fork(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_policy;

	retval = perf_event_init_task(p, clone_flags);
	if (retval)
		goto bad_fork_sched_cancel_fork;
	retval = audit_alloc(p);
	if (retval)
		goto bad_fork_cleanup_perf;
	/* copy all the process information */
	shm_init_task(p);
	retval = security_task_alloc(p, clone_flags);
	if (retval)
		goto bad_fork_cleanup_audit;
	retval = copy_semundo(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_security;
	retval = copy_files(clone_flags, p, args->no_files);
	if (retval)
		goto bad_fork_cleanup_semundo;
	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;
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(p, args);
	if (retval)
		goto bad_fork_cleanup_io;

	stackleak_task_init(p);

	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)) {
			retval = PTR_ERR(pid);
			goto bad_fork_cleanup_thread;
		}
	}

	/*
	 * This has to happen after we've potentially unshared the file
	 * descriptor table (so that the pidfd doesn't leak into the child
	 * if the fd table isn't shared).
	 */
	if (clone_flags & CLONE_PIDFD) {
		int flags = (clone_flags & CLONE_THREAD) ? PIDFD_THREAD : 0;

		/*
		 * Note that no task has been attached to @pid yet indicate
		 * that via CLONE_PIDFD.
		 */
		retval = pidfd_prepare(pid, flags | PIDFD_STALE, &pidfile);
		if (retval < 0)
			goto bad_fork_free_pid;
		pidfd = retval;

		retval = put_user(pidfd, args->pidfd);
		if (retval)
			goto bad_fork_put_pidfd;
	}

#ifdef CONFIG_BLOCK
	p->plug = NULL;
#endif
	futex_init_task(p);

	/*
	 * sigaltstack should be cleared when sharing the same VM
	 */
	if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM)
		sas_ss_reset(p);

	/*
	 * Syscall tracing and stepping should be turned off in the
	 * child regardless of CLONE_PTRACE.
	 */
	user_disable_single_step(p);
	clear_task_syscall_work(p, SYSCALL_TRACE);
#if defined(CONFIG_GENERIC_ENTRY) || defined(TIF_SYSCALL_EMU)
	clear_task_syscall_work(p, SYSCALL_EMU);
#endif
	clear_tsk_latency_tracing(p);

	/* ok, now we should be set up.. */
	p->pid = pid_nr(pid);
	if (clone_flags & CLONE_THREAD) {
		p->group_leader = current->group_leader;
		p->tgid = current->tgid;
	} else {
		p->group_leader = p;
		p->tgid = p->pid;
	}

	p->nr_dirtied = 0;
	p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
	p->dirty_paused_when = 0;

	p->pdeath_signal = 0;
	p->task_works = NULL;
	clear_posix_cputimers_work(p);

#ifdef CONFIG_KRETPROBES
	p->kretprobe_instances.first = NULL;
#endif
#ifdef CONFIG_RETHOOK
	p->rethooks.first = NULL;
#endif

	/*
	 * Ensure that the cgroup subsystem policies allow the new process to be
	 * forked. It should be noted that the new process's css_set can be changed
	 * between here and cgroup_post_fork() if an organisation operation is in
	 * progress.
	 */
	retval = cgroup_can_fork(p, args);
	if (retval)
		goto bad_fork_put_pidfd;

	/*
	 * Now that the cgroups are pinned, re-clone the parent cgroup and put
	 * the new task on the correct runqueue. All this *before* the task
	 * becomes visible.
	 *
	 * This isn't part of ->can_fork() because while the re-cloning is
	 * cgroup specific, it unconditionally needs to place the task on a
	 * runqueue.
	 */
	retval = sched_cgroup_fork(p, args);
	if (retval)
		goto bad_fork_cancel_cgroup;

	/*
	 * Allocate a default futex hash for the user process once the first
	 * thread spawns.
	 */
	if (need_futex_hash_allocate_default(clone_flags)) {
		retval = futex_hash_allocate_default();
		if (retval)
			goto bad_fork_cancel_cgroup;
		/*
		 * If we fail beyond this point we don't free the allocated
		 * futex hash map. We assume that another thread will be created
		 * and makes use of it. The hash map will be freed once the main
		 * thread terminates.
		 */
	}
	/*
	 * From this point on we must avoid any synchronous user-space
	 * communication until we take the tasklist-lock. In particular, we do
	 * not want user-space to be able to predict the process start-time by
	 * stalling fork(2) after we recorded the start_time but before it is
	 * visible to the system.
	 */

	p->start_time = ktime_get_ns();
	p->start_boottime = ktime_get_boottime_ns();

	/*
	 * Make it visible to the rest of the system, but dont wake it up yet.
	 * Need tasklist lock for parent etc handling!
	 */
	write_lock_irq(&tasklist_lock);

	/* CLONE_PARENT re-uses the old parent */
	if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
		p->real_parent = current->real_parent;
		p->parent_exec_id = current->parent_exec_id;
		if (clone_flags & CLONE_THREAD)
			p->exit_signal = -1;
		else
			p->exit_signal = current->group_leader->exit_signal;
	} else {
		p->real_parent = current;
		p->parent_exec_id = current->self_exec_id;
		p->exit_signal = args->exit_signal;
	}

	klp_copy_process(p);

	sched_core_fork(p);

	spin_lock(&current->sighand->siglock);

	rv_task_fork(p);

	rseq_fork(p, clone_flags);

	/* Don't start children in a dying pid namespace */
	if (unlikely(!(ns_of_pid(pid)->pid_allocated & PIDNS_ADDING))) {
		retval = -ENOMEM;
		goto bad_fork_core_free;
	}

	/* Let kill terminate clone/fork in the middle */
	if (fatal_signal_pending(current)) {
		retval = -EINTR;
		goto bad_fork_core_free;
	}

	/* No more failure paths after this point. */

	/*
	 * Copy seccomp details explicitly here, in case they were changed
	 * before holding sighand lock.
	 */
	copy_seccomp(p);

	init_task_pid_links(p);
	if (likely(p->pid)) {
		ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

		init_task_pid(p, PIDTYPE_PID, pid);
		if (thread_group_leader(p)) {
			init_task_pid(p, PIDTYPE_TGID, pid);
			init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
			init_task_pid(p, PIDTYPE_SID, task_session(current));

			if (is_child_reaper(pid)) {
				ns_of_pid(pid)->child_reaper = p;
				p->signal->flags |= SIGNAL_UNKILLABLE;
			}
			p->signal->shared_pending.signal = delayed.signal;
			p->signal->tty = tty_kref_get(current->signal->tty);
			/*
			 * Inherit has_child_subreaper flag under the same
			 * tasklist_lock with adding child to the process tree
			 * for propagate_has_child_subreaper optimization.
			 */
			p->signal->has_child_subreaper = p->real_parent->signal->has_child_subreaper ||
							 p->real_parent->signal->is_child_subreaper;
			list_add_tail(&p->sibling, &p->real_parent->children);
			list_add_tail_rcu(&p->tasks, &init_task.tasks);
			attach_pid(p, PIDTYPE_TGID);
			attach_pid(p, PIDTYPE_PGID);
			attach_pid(p, PIDTYPE_SID);
			__this_cpu_inc(process_counts);
		} else {
			current->signal->nr_threads++;
			current->signal->quick_threads++;
			atomic_inc(&current->signal->live);
			refcount_inc(&current->signal->sigcnt);
			task_join_group_stop(p);
			list_add_tail_rcu(&p->thread_node,
					  &p->signal->thread_head);
		}
		attach_pid(p, PIDTYPE_PID);
		nr_threads++;
	}
	total_forks++;
	hlist_del_init(&delayed.node);
	spin_unlock(&current->sighand->siglock);
	syscall_tracepoint_update(p);
	write_unlock_irq(&tasklist_lock);

	if (pidfile)
		fd_install(pidfd, pidfile);

	proc_fork_connector(p);
	sched_post_fork(p);
	cgroup_post_fork(p, args);
	perf_event_fork(p);

	trace_task_newtask(p, clone_flags);
	uprobe_copy_process(p, clone_flags);
	user_events_fork(p, clone_flags);

	copy_oom_score_adj(clone_flags, p);

	return p;

bad_fork_core_free:
	sched_core_free(p);
	spin_unlock(&current->sighand->siglock);
	write_unlock_irq(&tasklist_lock);
bad_fork_cancel_cgroup:
	cgroup_cancel_fork(p, args);
bad_fork_put_pidfd:
	if (clone_flags & CLONE_PIDFD) {
		fput(pidfile);
		put_unused_fd(pidfd);
	}
bad_fork_free_pid:
	if (pid != &init_struct_pid)
		free_pid(pid);
bad_fork_cleanup_thread:
	exit_thread(p);
bad_fork_cleanup_io:
	if (p->io_context)
		exit_io_context(p);
bad_fork_cleanup_namespaces:
	exit_task_namespaces(p);
bad_fork_cleanup_mm:
	if (p->mm) {
		mm_clear_owner(p->mm, p);
		mmput(p->mm);
	}
bad_fork_cleanup_signal:
	if (!(clone_flags & CLONE_THREAD))
		free_signal_struct(p->signal);
bad_fork_cleanup_sighand:
	__cleanup_sighand(p->sighand);
bad_fork_cleanup_fs:
	exit_fs(p); /* blocking */
bad_fork_cleanup_files:
	exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
	exit_sem(p);
bad_fork_cleanup_security:
	security_task_free(p);
bad_fork_cleanup_audit:
	audit_free(p);
bad_fork_cleanup_perf:
	perf_event_free_task(p);
bad_fork_sched_cancel_fork:
	sched_cancel_fork(p);
bad_fork_cleanup_policy:
	lockdep_free_task(p);
#ifdef CONFIG_NUMA
	mpol_put(p->mempolicy);
#endif
bad_fork_cleanup_delayacct:
	delayacct_tsk_free(p);
bad_fork_cleanup_count:
	dec_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1);
	exit_creds(p);
bad_fork_free:
	WRITE_ONCE(p->__state, TASK_DEAD);
	exit_task_stack_account(p);
	put_task_stack(p);
	delayed_free_task(p);
fork_out:
	spin_lock_irq(&current->sighand->siglock);
	hlist_del_init(&delayed.node);
	spin_unlock_irq(&current->sighand->siglock);
	return ERR_PTR(retval);
}

内核线程

内核线程是直接由内核本身启动的进程,其实际上是将内核函数委托给独立的进程,与系统中其他进程“并行”执行,有两种类型的内核线程:

  1. 线程启动后一直等待,直至内核请求线程执行某一特定操作。
  2. 线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制值时采取行动。内核使用这类线程用于连续监测任务。

内核线程与其他线程的不同:

  1. 它们在CPU的管态(supervisor mode)执行,而不是用户状态。
  2. 它们只可以访问虚拟地址空间的内核部分(高于TASK_SIZE 的所有地址),但不能访问用户空间。

运行新程序

Linux提供的execve系统调用通过用新代码替换现存程序,启动新程序,类似于fork(系统调用)->sys_fork(依托于架构体系的处理)->do_fork(无关架构体系的函数),execve(系统调用)->sys_execve(依托于架构体系的处理)->do_execve函数。

运行新程序的实现 运行新程序的实现

do_execve 函数签名

1
2
3
4
int do_execve(const char * filename,     /* 可执行文件名称 */
  const char __user *const __user *argv, /* 程序的参数 */
  const char __user *const __user *envp, /* 环境的指针 */
  struct pt_regs * regs /* 寄存器*/)

进程退出

Linux进程必须用exit系统调用终止进程使内核将进程使用的资源释放回系统。该调用的入口点是sys_exit 函数,需要一个错误码作为其参数,以便退出进程,其将工作大部分委托给了do_exit函数。该函数的实现就是将各个引用计数器减1,如果引用计数器归0而没有进程再使用对应的结构,那么将相应的内存区域返还给内存管理模块。

本文由作者按照 CC BY 4.0 进行授权