2.4 Linux进程

1、进程创建

.1、clone()

clone()、fork()、vfork()实际调用的都是do_fork()函数,只是带入的参数不一样。Clone()是最灵活的函数,参数可以自定义,但也只是一般拿来创建线程,被创建线程函数pthread_create()调用。Fork()创建进程,vfork创建共享用户空间的进程。

1.1.1、pthread_create()

创建线程一般不会直接调用clone()函数,而是调用glibc中的pthread_create(),pthread_create再构造创建线程参数,来调用clone()函数。

1
2
3
4
5
6
7
8
9
10
11

1.1.2、sys_clone()

12

1.1.3、do_fork()

13
14

1.1.4、copy_process()

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

1.1.5、wake_up_new_task()

30
31
32

1.2、fork()

1.2.1、sys_fork()

33

1.3、vfork()

1.3.1、sys_vfork()

34

1.4、kernel_thread()

35

1.4.1、进程0

Arch/i386/kernel/head.S中的startup_32()函数中创建进程0,进程0很多使用静态变量结构,最后跳转到start_kernel()函数。

1.4.2、进程1(init进程)

在start_kernel()函数中调用kernel_thread()创建init内核进程,即进程1,init内核进程最后通过execve()调用用户态的程序“/sbin/init”,从而变成了一个用户态进程。

1.5、exit()

1.5.1、sys_exit()

36

1.5.2、do_exit()

37
38
39
40
41
42
43
44
45

1.6、exit_group()

1.6.1、sys_ exit_group()

46

1.6.2、do_group_exit()

47
48

1.7、waitpid()

1.7.1、sys_ waitpid()

49
50

1.7.2、do_ wait()

51
52
53
54
55
56
57
58
59

1.8、kill()

1.8.1、sys_ kill()

1.9、getpid()

60

1.9.1、execv()

2、进程调度

2.1、进程优先级

Linux支持实时进程和普通分时进程。实时进程是只要不主动进入阻塞永远处于TASK_RUNNING状态;普通的分时进程,系统会根据优先级和运行状态给其分配运行时间片,时间片耗尽即被切换成其他进程。

系统给实时进程分配的静态优先级为0-99,给普通分时进程分配的静态优先级为100-139,数值越低优先级越高。

一般进程创建都是都是普通分时进程,静态优先级从父进程继承,默认的优先级为120。还有一个相关的nice值,nice的取值为(-20 , 19),优先级为120时nice为0,优先级为100的nice为-20,优先级139的nice为19。

静态优先级为基础,系统还会根据分时进程的历史运行情况给进程分配一个动态优先级,动态优先级真正决定了进程能分配到的时间片的大小。

根据历史运行情况和平均睡眠时间来动态调整分时进程的优先级,即为分时进程的调度策略。主要思想是既能区分出进程的不同应用类型,有能让每个进程都有机会得到运行。实现动态优先级的代码为effective_prio()函数。

实时进程的调度算法比较简单,没有动态优先级:一种为SCHED_FIFO类型,这种进程完全由优先级调度,高优先级的任务想运行多久就运行多久,即使有其他相同优先级的实时进程存在;另一种为SCHED_RR类型,时间片用完会切换出来,但是并不过期还是加入到active队列,让其他相同优先级的SCHED_RR类型实时进程能够得到运行。

2.2、进程切换时间点

弄清进程切换的时间点是理解进程调度的关键。

真正进行进程切换的函数是schedule(),该函数会从active队列中选取一个优先级最高的TASK_RUNNING状态进程,从当前进程切换到选出的进程。

发生进程切换的时间点有几个:

  • 1、 进程进入阻塞状态(立即切换)。进程调用会造成阻塞的系统函数,比如信号量、sleep延时、加入等待队列等等,如果进入阻塞模式,会将进程的状态由TASK_RUNNING置为TASK_INTERRUPTBLE/TASK_INTERRUPTBLE,随后再调用schedule()函数切到其他进程;
  • 2、 系统tick处理函数(延时切换)。在cpu timer中断中调用tick处理函数scheduler_tick(),在其中会处理分时进程的时间片,如果分时进程的时间片耗完scheduler_tick()函数会将进程移出active队列,进程的状态还是TASK_RUNNING。需要注意的是scheduler_tick()并不会马上调用schedule()函数进行进程切换,实际的进程切换发生在中断处理函数退出或者是系统调用完成进程从内核态返回到用户态。
  • 3、 唤醒进程(延时切换)。用户用完资源释放信号量,或是唤醒等待队列中的进程等等,会唤醒处于阻塞状态的进程,会将进程的状态由TASK_INTERRUPTBLE/TASK_INTERRUPTBLE置为TASK_RUNNING。但是会和tick一样并不会马上进行调用schedule()函数进行进程切换。
  • 4、 系统调用完成进程从内核态返回到用户态(延时切换点)。在这个时刻会检查是否需要切换,如发生过上述2、3的延时切换,这里就调用schedule()函数进行进程切换。
  • 5、 中断处理函数退出到用户态(延时切换点)。在中断处理函数中,通常伴随着资源的释放,即意味着会调用上述3的操作来唤醒阻塞进程,不过这也不是立即产生调度,只有在中断返回到用户态之前才会调用schedule()函数进行进程切换。
  • 6、 中断处理函数退出到内核态(抢占切换点)。如果是在内核态中发生中断的,中断处理完成会返回内核态,如果是抢占式内核在这个时间点会重新调用schedule()函数进行进程切换,如果是非抢占式内核只会简单的返回原进程被中断打断的位置继续执行。

2.3、进程抢占

抢占的概念:是指被阻塞的进程变为就绪后,是否能马上切换到就绪进程。

进程由阻塞变为就绪的触发事件是中断(外部中断或者tick中断),中断处理函数中调用系统函数唤醒进程,如果唤醒的进程优先级大于当前进程系统期望立即进行进程切换。

如果是在内核态中发生中断的,中断处理完成会返回内核态。在这种情况下:如果是抢占式内核在这个时间点会重新调用schedule()函数进行进程切换,如果是非抢占式内核只会简单的返回原进程被中断打断的位置继续执行。

有些时候进程在执行不可打断的关键路径(比如和硬件交互),不希望被抢占走。可以使用宏preempt_disable()来禁止抢占,关键路径执行完,使用宏preempt_disable()来使能抢占。

2.4、进程的多CPU调度

进程在多cpu的smp系统上是动态调度的,每个进程某一时刻只能运行在某一个cpu上,每个cpu会承担所有进程中的某几个进程。

系统会根据cpu上进程的负载动态的调整cpu上的进程集,相当于进程被动态的分配到某个cpu上运行。这个调试操作是在tick处理函数中调用rebalance_tick()函数完成的。

进程可以设置能在哪几个cpu进行调度,这就是进程的cpu亲和力p->cpus_allowed。把进程的cpu亲和力设置成某一个cpu,就叫cpu绑定。

3、参考资料

posted @ 2017-10-14 16:22  pwl999  阅读(65)  评论(0)    收藏  举报