linux内核3-进程管理

进程概述

状态转换

在这里插入图片描述

#define TASK_RUNNING		0
#define TASK_INTERRUPTIBLE	1
#define TASK_UNINTERRUPTIBLE	2
#define __TASK_STOPPED		4
#define __TASK_TRACED		8
...

进程亲属关系

树形结构体

  1. real_parent 创建当前进程的进程, parent相当于养父, 比如:父进程销毁,子进程归init进程管理;
  2. 线程组: 一个进程创建多个线程, 线程才是内核的调度单位, 线程也拥有自己的pid, gettid()获取。参考http://www.it165.net/os/html/201305/5123.html
    在这里插入图片描述

进程控制块如何存放

为了节省空间,linux把内核栈和一个紧挨PCB的小数据结构thread_info发在一起, 占用8KB内存区。
在这里插入图片描述

thread_info的第一个字段就是task_struct的指针。把PCB与内核栈放在一块的作用:

  1. 内核可以方便而快速低找到PCB, 只要知道栈指针, 就可以找到PCB的起始地址, 用伪代码描述为:
    p = (struct stask_struct*)STACK_POINT & 0xfffe000
  2. 避免在创建进程时动态分配额外的内存, 栈空间也不会受到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

进程的优先级

  • 用户空间
  1. 普通优先级 nice:-2-19
  2. 调度优先级 scheduling priority: 1 -99
    在这里插入图片描述
  • 内核空间
  1. 动态优先级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.功耗小

参考资料:内核 第三章、第七章, 窝窝网站

猜你喜欢

转载自blog.csdn.net/CPriLuke/article/details/110892951