基于Linux2.6.33分析进程模型
Posted 嘉轩大
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Linux2.6.33分析进程模型相关的知识,希望对你有一定的参考价值。
一、操作系统是怎么组织进程的
进程是操作系统的资源分配和独立运行的基本单位。它一般由以下三个部分组成
进程控制块
进程创建时,操作系统就新建一个PCB结构,它之后就常驻内存,任一时刻可以存取, 在进程结束时删除。PCB是进程实体的一部分,是进程存在的唯一标志。
当创建一个进程时,系统为该进程建立一个PCB;当进程执行时,系统通过其PCB 了 解进程的现行状态信息,以便对其进行控制和管理;当进程结束时,系统收回其PCB,该进 程随之消亡。操作系统通过PCB表来管理和控制进程。
进程描述信息 | 进程控制和管理信息 | 资源分配清单 | 处理机相关信息 |
---|---|---|---|
进程标识符(PID) | 进程当前状态 | 代码段指针 | 通用寄存器值 |
用户标识符(UID) | 进程优先级 | 数据段指针 | 地址寄存器值 |
代码运行入口地址 | 堆栈段指针 | 控制寄存器值 | |
程序的外存地址 | 文件描述符 | 标志寄存器值 | |
进入内存时间 | 键盘 | 状态字 | |
处理机占用时间 | 鼠标 | ||
信号量使用 |
该表是一个PCB的实例,PCB主要包括进程描述信息、进程控制和管理信息、资源 分配清单和处理机相关信息等。各部分的主要说明如下:
1) 进程描述信息
进程标识符:标志各个进程,每个进程都有一个并且是唯一的标识号。
用户标识符:进程归属的用户,用户标识符主要为共享和保护服务。
2) 进程控制和管理信息
进程当前状态:描述进程的状态信息,作为处理机分配调度的依据。
进程优先级:描述进程抢占处理机的优先级,优先级高的进程可以优先获得处理机。
3) 资源分配清单,用于说明有关内存地址空间或虚拟地址空间的状况;所打开文件的 列表和所使用的输入/输出设备信息。
4) 处理机相关信息,主要指处理机中各寄存器值,当进程被切换时,处理机状态信息 都必须保存在相应的PCB中,以便在该进程重新执行时,能再从断点继续执行。
在一个系统中,通常存在着许多进程,有的处于就绪状态,有的处于阻塞状态,而且阻塞的原因各不相同。为了方便进程的调度和管理,需要将各进程的PCB用适当的方法组织起来。目前,常用的组织方式有链接方式和索引方式两种。链接方式将同一状态的PCB链接成一个队列,不同状态对应不同的队列,也可以把处于阻塞状态的进程的PCB,根据其阻塞原因的不同,排成多个阻塞队列。索引方式是将同一状态的进程组织在一个索引表中,索引表的表项指向相应的PCB,不同状态对应不同的索引表,如就绪索引表和阻塞索引表等。
程序段
程序段就是能被进程调度程序调度到CPU执行的程序代码段。注意,程序可以被多个进程共享,就是说多个进程可以运行同一个程序。
数据段
一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果。
进程管理数据结构分析
/*task_struct 进程描述符*/ struct task_struct { /*进程的运行状态*/ volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ /*stack进程内核栈*/ /* * 进程通过alloc_thread_info()分配它的内核栈, * 通过free_thread_info()释放它的内核栈 * 两个函数定义在<asm/thread_info.h>中. * thread_info是进程的另一个内核数据结构,存放在进程内核栈的尾端. * thread_info内部的task域存放指向该任务实际的task_struct. * linux内核栈是由联合体thread_union表示的,定义在<linux/sched.h>中. */ void *stack; atomic_t usage; unsigned int flags; /* per process flags, defined below */ unsigned int ptrace; int lock_depth; /* BKL lock depth */ #ifdef CONFIG_SMP #ifdef __ARCH_WANT_UNLOCKED_CTXSW int oncpu; #endif #endif /* * 内核中规定,进程的优先级范围为[0,MAX_PRIO-1].其中分为实时进程部分: * [0,MAX_RT_PRIO-1]和非实时进程部分:[MAX_RT_PRIO,MAX_PRIO-1]. * 优先级值越小,意味着优先级别越高,任务先被内核调度. * prio 指任务当前的动态优先级,其值影响任务的调度顺序. * normal_prio指的是任务的常规优先级,该值基于static_prio和调度策略计算 * static_prio值得是任务的静态优先级,在进程创建时分配,该值会影响分配给 * 任务的时间片的长短和非实时任务动态优先级的计算. * rt_prioity指的是任务的实时优先级.0表示普通任务,[1,99]表示实时任务. * 值越大,优先级越高 * 对于实时进程:prio = normal_prio = static_prio * 对于普通进程:prio = normal_prio = MAX_RT_PRIO -1 -rt_priority * prio的值在使用实时互斥量时会暂时提升,释放后恢复成normal_prio */ int prio, static_prio, normal_prio; const struct sched_class *sched_class; /*sched_entity se 调度器实体 用来对进程运行时间做记账*/ struct sched_entity se; struct sched_rt_entity rt; #ifdef CONFIG_PREEMPT_NOTIFIERS /* list of struct preempt_notifier: */ struct hlist_head preempt_notifiers; #endif /* * fpu_counter contains the number of consecutive context switches * that the FPU is used. If this is over a threshold, the lazy fpu * saving becomes unlazy to save the trap. This is an unsigned char * so that after 256 times the counter wraps and the behavior turns * lazy again; this to deal with bursty apps that only use FPU for * a short time */ unsigned char fpu_counter; s8 oomkilladj; /* OOM kill score adjustment (bit shift). */ #ifdef CONFIG_BLK_DEV_IO_TRACE unsigned int btrace_seq; #endif /* * 调度策略 实时进程FIFO/RR or OTHER */ unsigned int policy; cpumask_t cpus_allowed; #ifdef CONFIG_PREEMPT_RCU int rcu_read_lock_nesting; int rcu_flipctr_idx; #endif /* #ifdef CONFIG_PREEMPT_RCU */ #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT) struct sched_info sched_info; #endif struct list_head tasks; /* * ptrace_list/ptrace_children forms the list of my children * that were stolen by a ptracer. */ struct list_head ptrace_children; struct list_head ptrace_list; /* * mm域存放了进程使用的内存描述符,内核线程的此域值为NULL * active_mm域存放当前活动的内存描述符,内核线程把前一个活动进程的 * mm域值存入此域,并作为临时地址空间执行程序. */ struct mm_struct *mm, *active_mm; /* task state */ struct linux_binfmt *binfmt; /* * exit_state进程的退出状态 */ int exit_state; int exit_code, exit_signal; int pdeath_signal; /* The signal sent when the parent dies */ /* ??? */ unsigned int personality; unsigned did_exec:1; /* * pid进程标识符 */ pid_t pid; /* * tgid进程组标识符 * POSIX标准规定,一个多线程应用程序中的所有线程必须有相同的PID * 在linux中,一个线程组的所有线程使用与该组的领头线程pid相同的值 * 作为线程组id,并存入tgid域中. * 另:使用getpid()系统调用得到的是tgid而非pid */ pid_t tgid; #ifdef CONFIG_CC_STACKPROTECTOR /* Canary value for the -fstack-protector gcc feature */ unsigned long stack_canary; #endif /* * pointers to (original) parent process, youngest child, younger sibling, * older sibling, respectively. (p->father can be replaced with * p->parent->pid) */ /* * 实际的父进程,父进程,仅在调试时才区分二者 */ struct task_struct *real_parent; /* real parent process (when being debugged) */ struct task_struct *parent; /* parent process */ /* * children/sibling forms the list of my children plus the * tasks I\'m ptracing. */ struct list_head children; /* list of my children */ struct list_head sibling; /* linkage in my parent\'s children list */ struct task_struct *group_leader; /* threadgroup leader */ /* PID/PID hash table linkage. */ struct pid_link pids[PIDTYPE_MAX]; /* *线程链表 */ struct list_head thread_group; struct completion *vfork_done; /* for vfork() */ int __user *set_child_tid; /* CLONE_CHILD_SETTID */ int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */ unsigned int rt_priority; cputime_t utime, stime, utimescaled, stimescaled; cputime_t gtime; cputime_t prev_utime, prev_stime; unsigned long nvcsw, nivcsw; /* context switch counts */ /* * 进程创建时间 */ struct timespec start_time; /* monotonic time */ /* * 进程实际的创建时间,基于系统启动时间 */ struct timespec real_start_time; /* boot based time */ /* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */ /* * 累计进程的次缺页数min_flt和主缺页数maj_flt */ unsigned long min_flt, maj_flt; cputime_t it_prof_expires, it_virt_expires; unsigned long long it_sched_expires; struct list_head cpu_timers[3]; /* process credentials */ /* * uid/gid运行该进程的用户的用户标识符和组标识符 * euid/egid有效的uid/gid * fsuid/fsgid文件系统的uid/gid 通常与euid/egid相同 * 在检查进程对文件系统的访问权限时使用fsuid/fsgid * suid/sgid为备份uid/gid */ uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; struct group_info *group_info; kernel_cap_t cap_effective, cap_inheritable, cap_permitted, cap_bset; unsigned keep_capabilities:1; struct user_struct *user; #ifdef CONFIG_KEYS struct key *request_key_auth; /* assumed request_key authority */ struct key *thread_keyring; /* keyring private to this thread */ unsigned char jit_keyring; /* default keyring to attach requested keys to */ #endif char comm[TASK_COMM_LEN]; /* executable name excluding path - access with [gs]et_task_comm (which lock it with task_lock()) - initialized normally by flush_old_exec */ /* file system info */ int link_count, total_link_count; #ifdef CONFIG_SYSVIPC /* ipc stuff */ struct sysv_sem sysvsem; #endif #ifdef CONFIG_DETECT_SOFTLOCKUP /* hung task detection */ unsigned long last_switch_timestamp; unsigned long last_switch_count; #endif /* CPU-specific state of this task */ /* * 用来标识进程的存储状态,具体实现依赖于特定的CPU架构 * 保存内核使用的相关任务状态段内容 */ struct thread_struct thread; /* filesystem information */ /* * 文件系统信息 */ struct fs_struct *fs; /* open file information */ /* * 打开文件表 */ struct files_struct *files; /* namespaces */ struct nsproxy *nsproxy; /* signal handlers */ struct signal_struct *signal; struct sighand_struct *sighand; sigset_t blocked, real_blocked; sigset_t saved_sigmask; /* To be restored with TIF_RESTORE_SIGMASK */ struct sigpending pending; unsigned long sas_ss_sp; size_t sas_ss_size; int (*notifier)(void *priv); void *notifier_data; sigset_t *notifier_mask; #ifdef CONFIG_SECURITY void *security; #endif struct audit_context *audit_context; #ifdef CONFIG_AUDITSYSCALL uid_t loginuid; unsigned int sessionid; #endif seccomp_t seccomp; /* Thread group tracking */ u32 parent_exec_id; u32 self_exec_id; /* Protection of (de-)allocation: mm, files, fs, tty, keyrings */ spinlock_t alloc_lock; /* Protection of the PI data structures: */ spinlock_t pi_lock; #ifdef CONFIG_RT_MUTEXES /* PI waiters blocked on a rt_mutex held by this task */ struct plist_head pi_waiters; /* Deadlock detection and priority inheritance handling */ struct rt_mutex_waiter *pi_blocked_on; #endif #ifdef CONFIG_DEBUG_MUTEXES /* mutex deadlock detection */ struct mutex_waiter *blocked_on; #endif #ifdef CONFIG_TRACE_IRQFLAGS unsigned int irq_events; int hardirqs_enabled; unsigned long hardirq_enable_ip; unsigned int hardirq_enable_event; unsigned long hardirq_disable_ip; unsigned int hardirq_disable_event; int softirqs_enabled; unsigned long softirq_disable_ip; unsigned int softirq_disable_event; unsigned long softirq_enable_ip; unsigned int softirq_enable_event; int hardirq_context; int softirq_context; #endif #ifdef CONFIG_LOCKDEP # define MAX_LOCK_DEPTH 48UL u64 curr_chain_key; int lockdep_depth; struct held_lock held_locks[MAX_LOCK_DEPTH]; unsigned int lockdep_recursion; #endif /* journalling filesystem info */ void *journal_info; /* stacked block device info */ struct bio *bio_list, **bio_tail; /* VM state */ struct reclaim_state *reclaim_state; struct backing_dev_info *backing_dev_info; struct io_context *io_context; unsigned long ptrace_message; siginfo_t *last_siginfo; /* For ptrace use. */ #ifdef CONFIG_TASK_XACCT /* i/o counters(bytes read/written, #syscalls */ u64 rchar, wchar, syscr, syscw; #endif struct task_io_accounting ioac; #if defined(CONFIG_TASK_XACCT) u64 acct_rss_mem1; /* accumulated rss usage */ u64 acct_vm_mem1; /* accumulated virtual memory usage */ cputime_t acct_stimexpd;/* stime since last update */ #endif #ifdef CONFIG_NUMA struct mempolicy *mempolicy; short il_next; #endif #ifdef CONFIG_CPUSETS nodemask_t mems_allowed; int cpuset_mems_generation; int cpuset_mem_spread_rotor; #endif #ifdef CONFIG_CGROUPS /* Control Group info protected by css_set_lock */ struct css_set *cgroups; /* cg_list protected by css_set_lock and tsk->alloc_lock */ struct list_head cg_list; #endif #ifdef CONFIG_FUTEX struct robust_list_head __user *robust_list; #ifdef CONFIG_COMPAT struct compat_robust_list_head __user *compat_robust_list; #endif struct list_head pi_state_list; struct futex_pi_state *pi_state_cache; #endif atomic_t fs_excl; /* holding fs exclusive resources */ struct rcu_head rcu; /* * cache last used pipe for splice */ struct pipe_inode_info *splice_pipe; #ifdef CONFIG_TASK_DELAY_ACCT struct task_delay_info *delays; #endif #ifdef CONFIG_FAULT_INJECTION int make_it_fail; #endif struct prop_local_single dirties; #ifdef CONFIG_LATENCYTOP int latency_record_count; struct latency_record latency_record[LT_SAVECOUNT]; #endif }; /*********************************************************************/ /* * thread_info简介 * 这是一个相对于task_struct结构要小很多的一个结构, * 每个内核线程都有一个thread_info结构,当进程从用户态陷入内核后,可以由 * thread_info中的task域指针来找到进程的task_struct. */ /*********************************************************************/ struct thread_info { unsigned long flags; /* low level flags */ int preempt_count; /* 0 => preemptable, <0 => bug */ mm_segment_t addr_limit; /* address limit */ /* * 相应的主任务的task_struct */ struct task_struct *task; /* main task structure */ /* * 执行域, * default_exec_domain 默认的执行域,定义在<kernel/exec_domain.c>中 */ struct exec_domain *exec_domain; /* execution domain */ __u32 cpu; /* cpu */ __u32 cpu_domain; /* cpu domain */ /* * 保存的CPU上下文,其成员为一系列CPU寄存器 */ struct cpu_context_save cpu_context; /* cpu context */ __u32 syscall; /* syscall number */ __u8 used_cp[16]; /* thread used copro */ unsigned long tp_value; struct crunch_state crunchstate; union fp_state fpstate __attribute__((aligned(8))); union vfp_state vfpstate; struct restart_block restart_block; }; /*********************************************************************/ /* * thread_union简介 * 内核栈的数据结构表示.内核栈是向下生长的, * 内核线程描述符thread_info分配在内核栈栈底,由于内核栈空间比 * thread_info结构体大很多,因此这样安排可以有效防止内存重叠. * 出于效率考虑,内核让8KB的内核栈占据两个连续的页框并让第一个页框的起始地址 * 是2^13的倍数. */ /*********************************************************************/ union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; }; /*********************************************************************/ /* * current_thread_info简介 * 下面这段代码是current_thread_info在ARM上的实现. * 由于内核栈起始地址是2^13的整数倍,因此在内核态, * 把当前SP&0x1fff得到的地址就是内核栈栈的基地址, * 即存放thread_info的地址. */ /*********************************************************************/ static inline struct thread_info *current_thread_info(void) { register unsigned long sp asm ("sp"); return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); }
二、进程状态如何转换
一个进程从创建而产生至撤销而消亡的整个生命周期,可以用一组状态加以刻划,根据三态模型,进程的生命周期可分为如下三种进程状态:
1. 运行态(running):占有处理器正在运行
2. 就绪态(ready):具备运行条件,等待系统分配处理器以便运行
3. 等待态(blocked):不具备运行条件,正在等待某个事件的完成
下图为进程转换图
三、进程是如何调度的
3.1、进程调度的定义
进程调度是操作系统进程管理的一个重要组成部分,其任务是选择下一个要运行的进程
3.2、进程调度目标
首先,一般的程序任务分为三种:CPU计算密集型、IO密集型与平衡(计算与IO各半)型,对于不同类型的程序,调度需要达到的目的也有所不同。对于IO密集型,响应时间最重要;对于CPU密集型,则周转时间最重要;而对于平衡型,进行某种响应和周转之间的平衡就显得比较重要。因此,进程调度的目标就是要达到极小化平均响应时间、极大化系统吞吐率、保持系统各个功能部件均处于繁忙状态和提供某种貌似公平的机制。
3.3、基本调度算法
3.3.1 先来服务算法
先来先服务(FCFS)算法是一种最常见的算法,它是人的本性中的一种公平观念。其优点就是简单且实现容易,缺点则是短的工作有可能变得很慢,因为其前面有很长的工作在执行,这样就会造成用户的交互式体验也比较差。
3.3.2 时间片轮转算法
时间片轮转是对FCFS算法的一种改进,其主要目的是改善短程序的响应时间,实现方式就是周期性地进行进程切换。时间片轮转的重点在于时间片的选择,需要考虑多方因素:如果运行的进程多时,时间片就需要短一些;进程数量少时,时间片就可以适当长一些。因此,时间片的选择是一个综合的考虑,权衡各方利益,进行适当折中。
3.3.3 优先级调度算法
优先级调度算法给每个进程赋予一个优先级,每次需要进程切换时,找一个优先级最高的进程进行调度。这样如果赋予长进程一个高优先级,则该进程就不会再“饥饿”。事实上,短任务优先算法本身就是一种优先级调度,只不过它给予短进程更高的优先级而已。
该算法的优点在于可以赋予重要的进程以高优先级以确保重要任务能够得到CPU时间,其缺点则有二:一是低优先级的进程可能会“饥饿”,二是响应时间无法保证。第一个缺点可以通过动态地调节任务的优先级解决,例如一个进程如果等待时间过长,其优先级将因持续提升而超越其他进程的优先级,从而得到CPU时间。第二个缺点可以通过将一个进程优先级设置为最高来解决,但即使将优先级设置为最高,但如果每个人都将自己的进程优先级设置为最高,其响应时间还是无法保证。
3.3.4混合调度算法
之前的算法都存在一定缺点,那么可否有一个算法混合他们的优点,摒弃它们的缺点,这就是所谓的混合调度算法。混合调度算法将所有进程分为不同的大类,每个大类为一个优先级。如果两个进程处于不同的大类,则处于高优先级大类的进程优先执行;如果处于同一个大类,则采用时间片轮转算法来执行。混合调度算法的示意图如下图所示:
3.4、进程的调度过程
3.5、CFS 调度器
CFS思路很简单,就是根据各个进程的权重分配运行时间。
进程的运行时间计算公式为:
分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和 (公式1)
vruntime = 实际运行时间 * 1024 / 进程权重 。 (公式2)
3.5.1 创建进程
void wake_up_new_task(struct task_struct *p, unsigned long clone_flags) { ..... if (!p->sched_class->task_new || !current->se.on_rq) { activate_task(rq, p, 0); } else { /* * Let the scheduling class do new task startup * management (if any): */ p->sched_class->task_new(rq, p); inc_nr_running(rq); } check_preempt_curr(rq, p, 0); ..... }
p->sched_class->task_new对应的函数是task_new_fair:
static void task_new_fair(struct rq *rq, struct task_struct *p) { struct cfs_rq *cfs_rq = task_cfs_rq(p); struct sched_entity *se = &p->se, *curr = cfs_rq->curr; int this_cpu = smp_processor_id(); sched_info_queued(p); update_curr(cfs_rq); place_entity(cfs_rq, se, 1); /* \'curr\' will be NULL if the child belongs to a different group */ if (sysctl_sched_child_runs_first && this_cpu == task_cpu(p) && curr && curr->vruntime < se->vruntime) { /* * Upon rescheduling, sched_class::put_prev_task() will place * \'current\' within the tree based on its new key value. */ swap(curr->vruntime, se->vruntime); resched_task(rq->curr); } enqueue_task_fair(rq, p, 0); }
place_entity计算新进程的vruntime:
static void place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial) { u64 vruntime = cfs_rq->min_vruntime; /* * The \'current\' period is already promised to the current tasks, * however the extra weight of the new task will slow them down a * little, place the new task so that it fits in the slot that * stays open at the end. */ if (initial && sched_feat(START_DEBIT)) vruntime += sched_vslice(cfs_rq, se); if (!initial) { //先不看这里, } se->vruntime = vruntime; }
check_preempt_curr(rq, p, 0);这个函数就直接调用了check_preempt_wakeup:
/* * Preempt the current task with a newly woken task if needed: */ 我略去了一些不太重要的代码 static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int sync) { struct task_struct *curr = rq->curr; struct sched_entity *se = &curr->se, *pse = &p->se; //se是当前进程,pse是新进程 /* * Only set the backward buddy when the current task is still on the * rq. This can happen when a wakeup gets interleaved with schedule on * the ->pre_schedule() or idle_balance() point, either of which can * drop the rq lock. * * Also, during early boot the idle thread is in the fair class, for * obvious reasons its a bad idea to schedule back to the idle thread. */ if (sched_feat(LAST_BUDDY) && likely(se->on_rq && curr != rq->idle)) set_last_buddy(se); set_next_buddy(pse); while (se) { if (wakeup_preempt_entity(se, pse) == 1) { resched_task(curr); break ; } se = parent_entity(se); pse = parent_entity(pse); } }
wakeup_preempt_entity(se, pse)判断后者是否能够抢占前者:
/* * Should \'se\' preempt \'curr\'. * * |s1 * |s2 * |s3 * g * |<--->|c * * w(c, s1) = -1 * w(c, s2) = 0 * w(c, s3) = 1 * */ static int wakeup_preempt_entity(struct sched_entity *curr, struct sched_entity *se) { s64 gran, vdiff = curr->vruntime - se->vruntime; if (vdiff <= 0) return -1; gran = wakeup_gran(curr); if (vdiff > gran) return 1; return 0; }
3.5.2 唤醒进程
/*** * try_to_wake_up - wake up a thread * @p: the to-be-woken-up thread * @state: the mask of task states that can be woken * @sync: do a synchronous wakeup? * * Put it on the run-queue if it\'s not already there. The "current" * thread is always on the run-queue (except when the actual * re-schedule is in progress), and as such you\'re allowed to do * the simpler "current->state = TASK_RUNNING" to mark yourself * runnable without the overhead of this. * * returns failure only if the task is already active. */ static int try_to_wake_up(struct task_struct *p, unsigned int state, int sync) { int cpu, orig_cpu, this_cpu, success = 0; unsigned long flags; struct rq *rq; rq = task_rq_lock(p, &flags); if (p->se.on_rq) goto out_running; update_rq_clock(rq); activate_task(rq, p, 1); success = 1; out_running: check_preempt_curr(rq, p, sync); p->state = TASK_RUNNING; out: current->se.last_wakeup = current->se.sum_exec_runtime; task_rq_unlock(rq, &flags); return success; }
3.5.3 进程调度schedule
/* * schedule() is the main scheduler function. */ asmlinkage以上是关于基于Linux2.6.33分析进程模型的主要内容,如果未能解决你的问题,请参考以下文章