原创作品转载请注明出处 ,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
一、task_struct结构体简要分析
1、进程状态宏定义
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 |
/* * file: linux-3.18.6/include/linux/sched.h */ 203 #define TASK_RUNNING 0 /* TASK_RUNNING表示就绪或者正在执行 * 是的,你没听错,它表示了两种进程状态 */ 204 #define TASK_INTERRUPTIBLE 1 /* 可中断休眠,进程被阻塞,等待某个信号或者资源可用 * 可以被信号和wake_up()唤醒,随后被设置为TASK_RUNNING */ 205 #define TASK_UNINTERRUPTIBLE 2 /*不可中断休眠,差别在于只能被wake_up()唤醒*/ 206 #define __TASK_STOPPED 4 /* 进程被暂停执行 */ 207 #define __TASK_TRACED 8 /* 进程被调试器跟踪 */ 208 /* in tsk->exit_state */ 209 #define EXIT_DEAD 16 210 #define EXIT_ZOMBIE 32 /* EXIT_DEAD表示进程的最终状态,zombie之后的状态,一切结束 * EXIT_ZOMBIE表示进程已终止,几乎所有资源已被回收, * 只是还占据着一个task_struct,等待父进程使用wait()等调用 */ 211 #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) 212 /* in tsk->state again */ 213 #define TASK_DEAD 64 /* 进程正在退出,随后进入zombie状态 */ 214 #define TASK_WAKEKILL 128 /* 在接收到致命信号时唤醒进程 */ 215 #define TASK_WAKING 256 216 #define TASK_PARKED 512 217 #define TASK_STATE_MAX 1024 |
可以看一下这幅流程图(来自《Linux kernel development》 )
图1. 进程状态的流程图
2、task_struct部分内容解释
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 |
/* * file: linux-3.18.6/include/linux/sched.h */ 1235 struct task_struct { 1236 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped * 这里是进程的状态 */ 1237 void *stack; 1238 atomic_t usage; /* 进程描述符使用计数. * 被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态 */ 1239 unsigned int flags; /* per process flags, defined below * 进程标识,在下面被定义 */ 1240 unsigned int ptrace; 1241 1242 #ifdef CONFIG_SMP ...... ...... /* 这段被省略的代码主要是多处理器支持 */ 1250 #endif 1251 int on_rq; 1252 1253 int prio, static_prio, normal_prio; 1254 unsigned int rt_priority; 1255 const struct sched_class *sched_class; 1256 struct sched_entity se; 1257 struct sched_rt_entity rt; /* 上面代码主要涉及到进程调度优先级的部分内容 * static_prio用于保存静态优先级,可以通过nice系统调用来进行修改 * rt_priority用于保存实时优先级 * normal_prio的值取决于静态优先级和调度策略 * prio用于保存动态优先级 */ ...... ...... 1268 #ifdef CONFIG_BLK_DEV_IO_TRACE 1269 unsigned int btrace_seq; /* 针对块设备I/O层的跟踪工具 */ 1270 #endif 1271 1272 unsigned int policy; /* policy表示进程的调度策略,目前主要有以下五种 * #define SCHED_NORMAL 0 * #define SCHED_FIFO 1 * #define SCHED_RR 2 * #define SCHED_BATCH 3 * SCHED_ISO: reserved but not implemented yet * #define SCHED_IDLE 5 * SCHED_NORMAL用于普通进程,通过CFS调度器实现 * SCHED_FIFO(先入先出调度算法)和SCHED_RR(轮流调度算法)都是实时调度策略 * SCHED_BATCH用于非交互的处理器消耗型进程 * SCHED_IDLE是在系统负载很低时使用 */ 1273 int nr_cpus_allowed; 1274 cpumask_t cpus_allowed; 1275 /* 这里是有关可用处理器个数的配置 */ ...... ...... 1291 #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) 1292 struct sched_info sched_info; /* 进程调度器相关信息 */ 1293 #endif 1294 1295 struct list_head tasks; /* 任务链表的头节点 */ 1296 #ifdef CONFIG_SMP 1297 struct plist_node pushable_tasks; 1298 struct rb_node pushable_dl_tasks; 1299 #endif /* 这里还是有关多处理器的配置 */ 1300 1301 struct mm_struct *mm, *active_mm; /* 内存管理相关的结构体 * mm指向进程所拥有的内存描述符,而active_mm指向进程运行时所使用的内存描述符。 * 对于普通进程而言,这两个指针变量的值相同。 * 但是,内核线程不 拥有任何内存描述符,所以它们的mm成员总是为NULL。 * 当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的 active_mm值 */ ...... ...... 1311 /* task state,下面是有关进程状态的代码 */ 1312 int exit_state; 1313 int exit_code, exit_signal; /* exit_code用于设置进程的终止代号。 * 这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)。 * exit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时, * 才会产生一个信号,以通知线程组的领头进程的父进程 */ 1314 int pdeath_signal; /* The signal sent when the parent dies */ /* pdeath_signal用于判断父进程终止时发送信号。*/ 1315 unsigned int jobctl; /* JOBCTL_*, siglock protected */ 1316 1317 /* Used for emulating ABI behavior of previous Linux versions */ 1318 unsigned int personality; /* personality用于处理不同的ABI,Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序 */ 1319 1320 unsigned in_execve:1; /* Tell the LSMs that the process is doing an 1321 * execve ,表示正在派生新的进程 */ 1322 unsigned in_iowait:1; 1323 1324 /* Revert to default priority/policy when forking */ /* 上面这句话说的很清楚啊,fork新进程的时候恢复默认优先级*/ 1325 unsigned sched_reset_on_fork:1; 1326 unsigned sched_contributes_to_load:1; 1327 1328 unsigned long atomic_flags; /* Flags needing atomic access. */ 1329 1330 pid_t pid; 1331 pid_t tgid; /* pid,进程标识符,表示唯一的一个进程。在CONFIG_BASE_SMALL配置为0的情况下,PID的取值范围是0到32767。 * * tpid, 在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID, * 并被存放在tgid成员中。只有线程组 的领头线程的pid成员才会被设置为与tgid相同的值。 * 注意,getpid()系统调用返回的是当前进程的tgid值而不是pid值 */ 1332 1333 #ifdef CONFIG_CC_STACKPROTECTOR 1334 /* Canary value for the -fstack-protector gcc feature */ 1335 unsigned long stack_canary; 1336 #endif /* 上面这段代码是堆栈保护相关的功能 */ 1337 /* 下面这段代码主要是进程亲属关系,代码注释很详细 1338 * pointers to (original) parent process, youngest child, younger sibling, 1339 * older sibling, respectively. (p->father can be replaced with 1340 * p->real_parent->pid) 1341 */ 1342 struct task_struct __rcu *real_parent; /* real parent process */ 1343 struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */ 1344 /* 1345 * children/sibling forms the list of my natural children 1346 */ 1347 struct list_head children; /* list of my children */ 1348 struct list_head sibling; /* linkage in my parent's children list */ 1349 struct task_struct *group_leader; /* threadgroup leader */ ...... ...... 1359 /* PID/PID hash table linkage. */ /* pid的hash表以及线程组链表 */ 1360 struct pid_link pids[PIDTYPE_MAX]; 1361 struct list_head thread_group; 1362 struct list_head thread_node; 1363 1364 struct completion *vfork_done; /* for vfork() */ 1365 int __user *set_child_tid; /* CLONE_CHILD_SETTID */ 1366 int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */ /* 如果copy_process函数的clone_flags参数的值被置为CLONE_CHILD_SETTID或 CLONE_CHILD_CLEARTID, * 则会把child_tidptr参数的值分别复制到set_child_tid和 clear_child_tid成员。 * 这些标志说明必须改变子进程用户态地址空间的child_tidptr所指向的变量的值 * / 1367 /* 下面一大段代码都是有关CPU时间的部分 */ 1368 cputime_t utime, stime, utimescaled, stimescaled; /* utime、stime分别记录进程在用户态、内核态下所经过的节拍数 * utimescaled/stimescaled也是用于记录进程在用户态/内核态的运行时间,但它们以处理器的频率为刻度 */ 1369 cputime_t gtime; /*gtime是以节拍计数的虚拟机运行时间(guest time)*/ 1370 #ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE 1371 struct cputime prev_cputime; 1372 #endif 1373 #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN 1374 seqlock_t vtime_seqlock; 1375 unsigned long long vtime_snap; 1376 enum { 1377 VTIME_SLEEPING = 0, 1378 VTIME_USER, 1379 VTIME_SYS, 1380 } vtime_snap_whence; 1381 #endif 1382 unsigned long nvcsw, nivcsw; /* context switch counts */ /* nvcsw、nivcsw是自愿(voluntary)、非自愿(involuntary)上下文切换计数 */ 1383 u64 start_time; /* monotonic time in nsec */ 1384 u64 real_start_time; /* boot based time in nsec */ 1385 /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ /* start_time和real_start_time都是进程创建时间, * real_start_time还包含了进程睡眠时间, * 常用于/proc/pid/stat */ 1386 unsigned long min_flt, maj_flt; 1387 1388 struct task_cputime cputime_expires; /*cputime_expires用来统计进程或进程组被跟踪的处理器时间 * 其中的三个成员对应着cpu_timers[3]的三个链表 */ 1389 struct list_head cpu_timers[3]; 1390 1391 /* process credentials ,进程权限相关 */ 1392 const struct cred __rcu *real_cred; /* objective and real subjective task 1393 * credentials (COW) */ 1394 const struct cred __rcu *cred; /* effective (overridable) subjective task 1395 * credentials (COW) */ 1396 char comm[TASK_COMM_LEN]; /* executable name excluding path 1397 * - access with [gs]et_task_comm (which lock 1398 * it with task_lock()) 1399 * - initialized normally by setup_new_exec */ 1400 /* file system info, 文件系统相关信息 */ 1401 int link_count, total_link_count; 1402 #ifdef CONFIG_SYSVIPC 1403 /* ipc stuff, 进程间通信相关 */ 1404 struct sysv_sem sysvsem; 1405 struct sysv_shm sysvshm; 1406 #endif 1407 #ifdef CONFIG_DETECT_HUNG_TASK 1408 /* hung task detection*/ 1409 unsigned long last_switch_count; /* last_switch_count是nvcsw和nivcsw的总和 */ 1410 #endif /* 下面是进程附属的其他一些信息 */ 1411 /* CPU-specific state of this task,CPU特有信息 */ 1412 struct thread_struct thread; 1413 /* filesystem information ,文件系统信息*/ 1414 struct fs_struct *fs; 1415 /* open file information ,打开的文件描述符*/ 1416 struct files_struct *files; 1417 /* namespaces ,命名空间*/ 1418 struct nsproxy *nsproxy; 1419 /* signal handlers ,信号处理*/ 1420 struct signal_struct *signal; 1421 struct sighand_struct *sighand; /* signal指向进程的信号描述符 * sighand指向进程的信号处理程序描述符。 * / 1422 1423 sigset_t blocked, real_blocked; /* blocked表示被阻塞信号的掩码 * real_blocked表示临时掩码 */ 1424 sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ 1425 struct sigpending pending; /* 待处理的信号 */ 1426 1427 unsigned long sas_ss_sp; 1428 size_t sas_ss_size; /* sas_ss_sp是信号处理程序备用堆栈的地址 * sas_ss_size表示堆栈的大小 */ 1429 int (*notifier)(void *priv); 1430 void *notifier_data; 1431 sigset_t *notifier_mask; /* 设备驱动程序常用notifier指向的函数来阻塞进程的某些信号 *(notifier_mask是这些信号的位掩码) * notifier_data指notifier所指向的函数可能使用的数据 * / 1432 struct callback_head *task_works; 1433 1434 struct audit_context *audit_context; 1435 #ifdef CONFIG_AUDITSYSCALL 1436 kuid_t loginuid; 1437 unsigned int sessionid; 1438 #endif /* 上面有关进程审计部分的代码 */ 1439 struct seccomp seccomp; /* Secure computing */ ...... ...... 1441 /* Thread group tracking, copy_process()函数使用的标记 */ 1442 u32 parent_exec_id; 1443 u32 self_exec_id; 1444 /* Protection of (de-)allocation: * mm, files, fs, tty, keyrings, mems_allowed, 1445 * mempolicy */ 1446 spinlock_t alloc_lock; /* 用于保护资源分配或释放的自旋锁 */ 1447 1448 /* Protection of the PI data structures: * 提供task_rq_lock()使用的锁 */ 1449 raw_spinlock_t pi_lock; 1450 1451 #ifdef CONFIG_RT_MUTEXES 1452 /* PI waiters blocked on a rt_mutex held by this task * 基于PI协议的等待互斥锁 */ 1453 struct rb_root pi_waiters; 1454 struct rb_node *pi_waiters_leftmost; 1455 /* Deadlock detection and priority inheritance handling */ 1456 struct rt_mutex_waiter *pi_blocked_on; 1457 #endif 1458 1459 #ifdef CONFIG_DEBUG_MUTEXES 1460 /* mutex deadlock detection,死锁检测 */ 1461 struct mutex_waiter *blocked_on; 1462 #endif 1463 #ifdef CONFIG_TRACE_IRQFLAGS 1464 unsigned int irq_events; 1465 unsigned long hardirq_enable_ip; 1466 unsigned long hardirq_disable_ip; 1467 unsigned int hardirq_enable_event; 1468 unsigned int hardirq_disable_event; 1469 int hardirqs_enabled; 1470 int hardirq_context; 1471 unsigned long softirq_disable_ip; 1472 unsigned long softirq_enable_ip; 1473 unsigned int softirq_disable_event; 1474 unsigned int softirq_enable_event; 1475 int softirqs_enabled; 1476 int softirq_context; 1477 #endif /* 上面是一堆关于中断的设置 */ ...... ...... 1487 /* journalling filesystem info */ 1488 void *journal_info; 1489 1490 /* stacked block device info */ 1491 struct bio_list *bio_list; 1492 1493 #ifdef CONFIG_BLOCK 1494 /* stack plugging */ 1495 struct blk_plug *plug; 1496 #endif 1497 1498 /* VM state */ 1499 struct reclaim_state *reclaim_state; 1500 /* 内存回收 */ 1501 struct backing_dev_info *backing_dev_info; 1502 /* 块设备信息 */ 1503 struct io_context *io_context; /* IO调度器信息 */ 1504 1505 unsigned long ptrace_message; 1506 siginfo_t *last_siginfo; /* For ptrace use. */ /* 下面是一些统计信息 */ 1507 struct task_io_accounting ioac; 1508 #if defined(CONFIG_TASK_XACCT) 1509 u64 acct_rss_mem1; /* accumulated rss usage */ 1510 u64 acct_vm_mem1; /* accumulated virtual memory usage */ 1511 cputime_t acct_timexpd; /* stime + utime since last update */ 1512 #endif ...... ...... /* 非一致内存访问相关数据 */ 1541 #ifdef CONFIG_NUMA 1542 struct mempolicy *mempolicy; /* Protected by alloc_lock */ 1543 short il_next; 1544 short pref_node_fork; 1545 #endif ...... ...... 1595 /* 这里是管道部分代码 1596 * cache last used pipe for splice 1597 */ 1598 struct pipe_inode_info *splice_pipe; 1599 1600 struct page_frag task_frag; |
二、系统调用以及do_fork()
sys_fork()、sys_vfork()、sys_clone( )三个系统调用都可以用来创建新的进程,最终都是调用的do_fork()这个函数。
但是在我们的环境中,fork()函数会使用sys_clone() 这个系统调用。
1、一些系统调用
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 |
/* * file: linux-3.18.6/kernel/fork.c */ /* 这里是sys_fork系统调用 */ 1703 SYSCALL_DEFINE0(fork) 1704 { 1705 #ifdef CONFIG_MMU 1706 return do_fork(SIGCHLD, 0, 0, NULL, NULL); 1707 #else 1708 /* can not support in nommu mode */ 1709 return -EINVAL; 1710 #endif 1711 } 1712 #endif 1713 /* 这里是sys_vfork系统调用 */ 1714 #ifdef __ARCH_WANT_SYS_VFORK 1715 SYSCALL_DEFINE0(vfork) 1716 { 1717 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0, 1718 0, NULL, NULL); 1719 } 1720 #endif 1721 /* 这里是sys_clone系统调用,由条件编译控制多个不同参数的clone调用 */ 1722 #ifdef __ARCH_WANT_SYS_CLONE 1723 #ifdef CONFIG_CLONE_BACKWARDS 1724 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 1725 int __user *, parent_tidptr, 1726 int, tls_val, 1727 int __user *, child_tidptr) 1728 #elif defined(CONFIG_CLONE_BACKWARDS2) 1729 SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags, 1730 int __user *, parent_tidptr, 1731 int __user *, child_tidptr, 1732 int, tls_val) 1733 #elif defined(CONFIG_CLONE_BACKWARDS3) 1734 SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp, 1735 int, stack_size, 1736 int __user *, parent_tidptr, 1737 int __user *, child_tidptr, 1738 int, tls_val) 1739 #else 1740 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, 1741 int __user *, parent_tidptr, 1742 int __user *, child_tidptr, 1743 int, tls_val) 1744 #endif 1745 { 1746 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); 1747 } 1748 #endif |
2、do_fork( )代码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* * file: linux-3.18.6/kernel/fork.c */ 1617 /* 1618 * Ok, this is the main fork-routine. 1619 * 1620 * It copies the process, and if successful kick-starts 1621 * it and waits for it to finish using the VM if required. 1622 */ 1623 long do_fork(unsigned long clone_flags, 1624 unsigned long stack_start, 1625 unsigned long stack_size, 1626 int __user *parent_tidptr, 1627 int __user *child_tidptr) 1628 { ...... ...... 1651 p = copy_process(clone_flags, stack_start, stack_size, 1652 child_tidptr, NULL, trace); /* 这里是关键代码,有关copy_process具体分析请见下一部分分析 */ ...... ...... 1691 } |
3、copy_process()函数代码
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 |
/* * file: linux-3.18.6/kernel/fork.c */ 1182 static struct task_struct *copy_process(unsigned long clone_flags, 1183 unsigned long stack_start, 1184 unsigned long stack_size, 1185 int __user *child_tidptr, 1186 struct pid *pid, 1187 int trace) 1188 { 1189 int retval; 1190 struct task_struct *p; 1191 ...... ...... 1240 p = dup_task_struct(current); /* 复制进程PCB,下面有具体分析 */ /* 下面大部分代码都是在做新进程资源的初始化操作*/ ...... ...... 1396 retval = copy_thread(clone_flags, stack_start, stack_size, p); /* 拷贝内核堆栈的一些数据,有关该函数的分析在下面 */ ...... ...... |
4、dup_task_struct()代码
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 |
/* * file: linux-3.18.6/kernel/fork.c */ 305 static struct task_struct *dup_task_struct(struct task_struct *orig) 306 { 307 struct task_struct *tsk; 308 struct thread_info *ti; 309 int node = tsk_fork_get_node(orig); 310 int err; 311 312 tsk = alloc_task_struct_node(node); /* 分配task_struct节点 */ 313 if (!tsk) 314 return NULL; 315 316 ti = alloc_thread_info_node(tsk, node); /* 分配task_info节点, * 函数代码是:struct page *page = alloc_kmem_pages_node(node, THREADINFO_GFP,THREAD_SIZE_ORDER); * 也就是分配了内核堆栈 */ 317 if (!ti) 318 goto free_tsk; 319 320 err = arch_dup_task_struct(tsk, orig); /* 这里面是最终的复制操作 * 其实就是这一句:*dst = *src; */ 321 if (err) 322 goto free_ti; 323 324 tsk->stack = ti; /* 将新分配的内核堆栈地址赋给新进程 */ 325 #ifdef CONFIG_SECCOMP 332 tsk->seccomp.filter = NULL; 333 #endif 334 335 setup_thread_stack(tsk, orig); /* 复制了task_thread_info相关信息*/ ...... ...... 364 } |
5、copy_thread()
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 |
/* * file: linux-3.18.6/arch/x86/kernel/process_32.c */ 132 int copy_thread(unsigned long clone_flags, unsigned long sp, 133 unsigned long arg, struct task_struct *p) 134 { 135 struct pt_regs *childregs = task_pt_regs(p); /* SAVE_ALL的时候保存的内核堆栈栈底的一些信息 */ 136 struct task_struct *tsk; 137 int err; 138 139 p->thread.sp = (unsigned long) childregs; 140 p->thread.sp0 = (unsigned long) (childregs+1); /* 复制了栈顶地址 */ 141 memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps)); 142 143 if (unlikely(p->flags & PF_KTHREAD)) { 144 /* kernel thread */ 145 memset(childregs, 0, sizeof(struct pt_regs)); 146 p->thread.ip = (unsigned long) ret_from_kernel_thread; 147 task_user_gs(p) = __KERNEL_STACK_CANARY; 148 childregs->ds = __USER_DS; 149 childregs->es = __USER_DS; 150 childregs->fs = __KERNEL_PERCPU; 151 childregs->bx = sp; /* function */ 152 childregs->bp = arg; 153 childregs->orig_ax = -1; 154 childregs->cs = __KERNEL_CS | get_kernel_rpl(); 155 childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED; 156 p->thread.io_bitmap_ptr = NULL; 157 return 0; 158 } 159 *childregs = *current_pt_regs(); /* 拷贝内核堆栈数据 */ 160 childregs->ax = 0; /* 修改返回值 */ 161 if (sp) 162 childregs->sp = sp; 163 164 p->thread.ip = (unsigned long) ret_from_fork; /* 这里是子进程的开始地址 !!!!!!!! * ret_from_fork是entry32.S中的入口 */ |
图4.简单流程图
三、gdb跟踪sys_clone()执行过程
1、在MenuOS中添加新函数
1 2 |
cd menu vim test.c |
1 2 3 4 5 6 7 8 9 10 |
int test_fork() { pid_t pid=0; pid=fork(); if(pid==0) printf("I am in the chind process\n"); else printf("I am in the parent process\n"); return 0; } |
2、生成新的根文件系统rootfs
1 |
make rootfs |
因为老师写好的makefile的原因,所以会和上次一样自动启动qemu虚拟机
3、开始跟踪sys_clone()
1 2 |
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S #启动虚拟机 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
gdb linux-3.18.6/vmlinux #打开带符号表的二进制文件 (gdb) target remote:1234 #连接qemu虚拟机 (gdb) c #继续执行,完成内核启动 Ctrl+c #在gdb中按下Ctrl+c,暂停虚拟机 (gdb) b sys_fork (gdb) b sys_vfork #设置两个断点 (gdb) c #继续执行 |
实际显示这两个断点并没有成功断下:
下面重新设值断点
1 2 3 4 |
(gdb) b sys_clone Breakpoint 6 at 0xc1043770: file kernel/fork.c, line 1724. (gdb) c Continuing. |
在虚拟机中运行test_fork之后,虚拟机被暂停了
因为博主实在没有时间。。。所以,下面的调试过程基本上没什么有价值的东西。。。
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 |
Breakpoint 7, SyS_clone (clone_flags=18874385, newsp=0, parent_tidptr=0, tls_val=0, child_tidptr=160082088) at kernel/fork.c:1724 1724 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, #这里可以看到代码停在了sys_clone()系统调用处 (gdb) s #‘s’,进入函数内部单步调试 SYSC_clone (tls_val=<optimized out>, child_tidptr=<optimized out>, parent_tidptr=<optimized out>, newsp=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:1746 1746 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); (gdb) #直接回车,重复上一条命令 SyS_clone (clone_flags=18874385, newsp=0, parent_tidptr=0, tls_val=0, child_tidptr=160082088) at kernel/fork.c:1724 1724 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, #这里第二次断下,说明clone调用在父子进程中被执行了两次 (gdb) #直接回车,重复上一条命令 SYSC_clone (tls_val=<optimized out>, child_tidptr=<optimized out>, parent_tidptr=<optimized out>, newsp=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:1746 1746 return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr); (gdb) #直接回车,重复上一条命令 do_fork (clone_flags=18874385, stack_start=0, stack_size=0, parent_tidptr=0x0, child_tidptr=0x98aa8a8) at kernel/fork.c:1639 1639 if (!(clone_flags & CLONE_UNTRACED)) { (gdb) n #这里是‘n’,直接向下执行,遇到函数时不进入函数内部 1628 { (gdb) #直接回车,重复上一条命令 1630 int trace = 0; (gdb) 1639 if (!(clone_flags & CLONE_UNTRACED)) { (gdb) 1640 if (clone_flags & CLONE_VFORK) (gdb) 1642 else if ((clone_flags & CSIGNAL) != SIGCHLD) (gdb) 1647 if (likely(!ptrace_event_enabled(current, trace))) (gdb) 1648 trace = 0; (gdb) 1651 p = copy_process(clone_flags, stack_start, stack_size, (gdb) 1657 if (!IS_ERR(p)) { (gdb) 1661 trace_sched_process_fork(current, p); (gdb) 1663 pid = get_task_pid(p, PIDTYPE_PID); (gdb) 1664 nr = pid_vnr(pid); (gdb) 1666 if (clone_flags & CLONE_PARENT_SETTID) (gdb) 1664 nr = pid_vnr(pid); (gdb) 1666 if (clone_flags & CLONE_PARENT_SETTID) (gdb) 1669 if (clone_flags & CLONE_VFORK) { (gdb) 1675 wake_up_new_task(p); (gdb) 1678 if (unlikely(trace)) (gdb) 1681 if (clone_flags & CLONE_VFORK) { (gdb) 1686 put_pid(pid); (gdb) 1691 } (gdb) SyS_clone (clone_flags=18874385, newsp=0, parent_tidptr=0, tls_val=0, child_tidptr=160082088) at kernel/fork.c:1724 1724 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, (gdb) schedule () at kernel/sched/core.c:2866 2866 { (gdb) 2867 struct task_struct *tsk = current; (gdb) finish #因为fork部分代码已经执行完毕,正在进行进程调度,所以结束调试 Run till exit from #0 schedule () at kernel/sched/core.c:2867 <signal handler called> |
四、个人总结
这次写博客花的时间确实不少,简直要命。。。因为想多写一点,以后可以参考,所以就对代码做了很多的注释。
Linux进程创建主要就是各种复制,首先复制父进程的大部分资源,甚至是内核堆栈信息,之后才对其进行修改,完成一个新进程信息的准备。最后设置各种堆栈以及寄存器,在ret_from_fork()之后就跳转到了新进程的起始代码处,开始了子进程的生命历程。当然,创建的最后是进程调度的时机,可能发生进程调度。
因为在Linux中没有真正的线程,线程的实现方式只是一种特殊的进程,所以我们一直没有仔细区分二者的差别。
Linux进程的创建实在不是一般的复杂,也不是三言两语能说清楚的,就说这么多了。
参考文献:
有关进程、线程区别的补充材料:
《Linux 线程实现机制分析》https://www.ibm.com/developerworks/cn/linux/kernel/l-thread/
《关于Linux的进程和线程》http://kenby.iteye.com/blog/1014039