进程概述
状态转换
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
...
进程亲属关系
树形结构体
- real_parent 创建当前进程的进程, parent相当于养父, 比如:父进程销毁,子进程归init进程管理;
- 线程组: 一个进程创建多个线程, 线程才是内核的调度单位, 线程也拥有自己的pid, gettid()获取。参考http://www.it165.net/os/html/201305/5123.html
进程控制块如何存放
为了节省空间,linux把内核栈和一个紧挨PCB的小数据结构thread_info发在一起, 占用8KB内存区。
thread_info的第一个字段就是task_struct的指针
。把PCB与内核栈放在一块的作用:
- 内核可以方便而快速低找到PCB, 只要知道栈指针, 就可以找到PCB的起始地址, 用伪代码描述为:
p = (struct stask_struct*)STACK_POINT & 0xfffe000
- 避免在创建进程时动态分配额外的内存, 栈空间也不会受到task的大小影响;
在linux, 为了表示当前正在运行的进程,定义了一个current的红,可以把它当做全局变量来用,例如current->pid返回正在运营的进程的标识符
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit;
struct restart_block restart_block;
...
};
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define THREAD_SIZE (PAGE_SIZE << THREAD_ORDER)
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
进程组织方式
linux进程是由各种各样的组织方式的, 常见的组织为双向链表,头指针为init_task
容器与进程关系
容器技术的核心功能就是通过约束和修改进行的动态表现,从而为其创造一个“边界” Cgroups制造约束,Namespace技术修改进程视图;
进程的创建
进程与线程
进程:资源分配单位
线程:系统运行单位
本质:内核使用同一的数据结构task_struct
来描述进程,线程,内核线程,也使用相同的调度算法对这三者进行调度;三者最终都是通过do_fork()进行创建的。
fork(): 写时复制
vfork(): 两种方式,父亲先睡觉,儿子运行;或者儿子共享父亲的空间,目前大多淘汰;
内核线程: kthread_create() 提供特定的标记创建
asmlinkage long sys_fork(struct pt_regs *regs)
{
return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}
asmlinkage long sys_vfork(struct pt_regs *regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0,
NULL, NULL);
}
asmlinkage long
sys_clone(unsigned long clone_flags, unsigned long newsp,
void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
if (!newsp)
newsp = regs->sp;
return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}
/**
* kthread_create - create a kthread.
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @namefmt: printf-style name for the thread.
*
* Description: This helper function creates and names a kernel
* thread. The thread will be stopped: use wake_up_process() to start
* it. See also kthread_run(), kthread_create_on_cpu().
*
* When woken, the thread will run @threadfn() with @data as its
* argument. @threadfn() can either call do_exit() directly if it is a
* standalone thread for which noone will call kthread_stop(), or
* return when 'kthread_should_stop()' is true (which means
* kthread_stop() has been called). The return value should be zero
* or a negative error number; it will be passed to kthread_stop().
*
* Returns a task_struct or ERR_PTR(-ENOMEM).
*/
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
create.threadfn = threadfn;
create.data = data;
init_completion(&create.started);
init_completion(&create.done);
spin_lock(&kthread_create_lock);
list_add_tail(&create.list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);
wait_for_completion(&create.done);
if (!IS_ERR(create.result)) {
va_list args;
va_start(args, namefmt);
vsnprintf(create.result->comm, sizeof(create.result->comm),
namefmt, args);
va_end(args);
}
return create.result;
}
do_fork()
copy_process()
创建进程控制块以及进程执行所需的数据结构, 子进程通过copy_xyz来获取共享资源,其一般的执行过程:
exec ,wait, exit
进程的生命周期,调用流程
fork() -> exec() -> exit() -> wait()打扫战场
进程调度
基本模型-就绪队列 - O(n)
查看2.4, task_struct->run_list
struct task_struct {
int prio, static_prio, normal_prio;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt; //里面有run_list
进程的优先级
- 用户空间
- 普通优先级 nice:-2-19
- 调度优先级 scheduling priority: 1 -99
- 内核空间
- 动态优先级prio,静态优先级 static_prio,归一化优先级normal_prio,实时优先级rt_priorit
O(1)调度模型
系统中所有的就绪进程首先经过负载均衡分配到各个cpu的就绪队列上,然后由主调度器和周期性调度器驱动该CPU上的调度行为。
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
/*
* This is the priority-queue data structure of the RT scheduling class:
*/
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
struct list_head queue[MAX_RT_PRIO];
};
BITMAP_SIZE表示各个优先级的列表是空还是非空。
基本过程:活跃队列active时间片耗尽后挂到expire链表,等所有活跃队列均指向完,再切换到expire队列;
主调度器:从该CPU的就绪队列中找到一个最适合的进程调用执行,基本思路
1. 通过负载均衡将各个就绪状态任务平均分配到各个CPU的就绪队列
2. 主调度器main_sheduler和周期性调度器tick scheduler的驱动下进行单个cpu上的调度
对于rt task, normal task, dead task 抽象共同的逻辑形成核心调度层(core sheduler layer),同时各种特定类型的调度器定义自己的调度类(sched class)
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);
void (*yield_task) (struct rq *rq);
int (*select_task_rq)(struct task_struct *p, int sync);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p);
struct task_struct * (*pick_next_task) (struct rq *rq);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
...
完全公平调度-CFS
CFS通过每个进程的虚拟运行时间(vruntime)来衡量哪个进程最值得被调度。
vruntime 通过 进程实际运行时间 和 进程权值 weight 计算, 越小越先被调度
调度总结
需求: 1. 公平, 2,快速响应 3.高的吞吐量 4.功耗小
参考资料:内核 第三章、第七章, 窝窝网站