linux进程调度(1)

一、 相关概念简述

1.1 Linux进程的四个要素

一般来说Linux系统的进程都具备下列诸要素:

(1) 有一段程序供其执行。这段程序不一定是某个进程所专有,可以与其他进程共用
(2) 有进程专用的内核空间堆栈
(3) 在内核中有一个task_struct数据结构,即通常所说的”进程控制块(PCB)“。有了这个数据结构,进程才能成为内核调度的一个基本单位接受内核的调度。同时,这个结构还记录着进程所占用的各项资源
(4) 有独立的存储空间,这意味着拥有专有的用户空间;进一步,还意味着除前述的内核空间堆栈外还有其专用的用户空间堆栈。有一点必须指出,内核空间是不能独立的,任何进程都不可能直接(不通过系统调用)改变内核空间的内容(除其本身的内核空间堆栈以外)

这四条都是必要条件,缺了任何一条都不能成为”进程”。如果只具备了前三条而缺第四条,就称为”线程“。如果完全没有用户空间,就称为”内核线程“(kernel thread);而如果共享用户空间则称为”用户线程“。二者往往都简称”线程”。

注意:事实上,在Linux系统中,进程和线程的区分并不十分严格,许多进程在”诞生”之初都与其父进程共用同一个存储空间,所以严格说来还是线程;但是子进程可以建立其自己的存储空间,并与父进程分离,成为真正意义上的进程。

1.2 task_struct结构描述

task_struct结构是向系统表明进程存在的唯一凭证,它包含了进程的全部信息。同时,也是进程为实现操作而取得必要资源的唯一途径。下面列出了task_struct结构的全部源码:

struct task_struct {
/* these are hardcoded - don't touch */
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    unsigned long flags;    /* per process flags, defined below */
    int sigpending;
    mm_segment_t addr_limit;  
      /* thread address space:
          0-0xBFFFFFFF for user-thead
         0-0xFFFFFFFF for kernel-thread
      */

    struct exec_domain *exec_domain;
    long need_resched;
/* various fields */
    long counter;
    long priority;
    cycles_t avg_slice;
/* SMP and runqueue state */
    int has_cpu;
    int processor;
    int last_processor;
    int lock_depth;     /* Lock depth. We can  context switch in and out of holding a syscall kernel lock... */    

    struct task_struct *next_task, *prev_task;
    struct list_head run_list;
/* task state */
    struct linux_binfmt *binfmt;
    int exit_code, exit_signal;
    int pdeath_signal;  /*  The signal sent when the parent dies  */
    /* ??? */
    unsigned long personality;
    int dumpable:1;
    int did_exec:1;
    pid_t pid;
    pid_t pgrp;
    pid_t tty_old_pgrp;
    pid_t session;
    /* boolean value for session group leader */
    int leader;
    /*
     * pointers to (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with  p->p_pptr->pid)
     */
    struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
    /* PID hash table linkage. */
    struct task_struct *pidhash_next;
    struct task_struct **pidhash_pprev;
    wait_queue_head_t wait_chldexit;    /* for wait4() */
    struct semaphore *vfork_sem;        /* for vfork() */
    unsigned long policy, rt_priority;
    unsigned long it_real_value, it_prof_value, it_virt_value;
    unsigned long it_real_incr, it_prof_incr, it_virt_incr;
    struct timer_list real_timer;
    struct tms times;
    unsigned long start_time;
    long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];
/* mm fault and swap info: this can arguably be seen as either mm-specific o
r thread-specific */
    unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
    int swappable:1;
/* process credentials */
    uid_t uid,euid,suid,fsuid;
    gid_t gid,egid,sgid,fsgid;
    int ngroups;
    gid_t   groups[NGROUPS];
    kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
    struct user_struct *user;
/* limits */
    struct rlimit rlim[RLIM_NLIMITS];
    unsigned short used_math;
    char comm[16];
/* file system info */
    int link_count;
    struct tty_struct *tty; /* NULL if no tty */
/* ipc stuff */
    struct sem_undo *semundo;
    struct sem_queue *semsleeping;
/* CPU-specific state of this task */
    struct thread_struct thread;
/* filesystem information */
    struct fs_struct *fs;
/* open file information */
    struct files_struct *files;
/* memory management info */
    struct mm_struct *mm, *active_mm;
/* signal handlers */
    spinlock_t sigmask_lock;    /* Protects signal and blocked */
    struct signal_struct *sig;
    sigset_t signal, blocked;
    struct signal_queue *sigqueue, **sigqueue_tail;
    unsigned long sas_ss_sp;
    size_t sas_ss_size;
};

对task_struct结构(参见include\linux\sched.h)各数据项的分类解释


  1. 调度数据成员
    (1) volatile long states;
    表示进程的当前状态:

· TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。
· TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。
· TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。
· TASK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块
·TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。导致这种状态的原因有二,或者是对收SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。
· TASK_SWAPPING: 进程页面被交换出内存的进程。
]

(2) unsigned long flags;
进程标志:

·PF_ALIGNWARN 打印”对齐”警告信息。
·PF_PTRACED 被ptrace系统调用监控。
·PF_TRACESYS 正在跟踪。
·PF_FORKNOEXEC 进程刚创建,但还没执行。
·PF_SUPERPRIV 超级用户特权。
·PF_DUMPCORE dumped core。
·PF_SIGNALED 进程被信号(signal)杀出。
·PF_STARTING 进程正被创建。
·PF_EXITING 进程开始关闭。
·PF_USEDFPU 该进程使用FPU(SMP only)。
·PF_DTRACE delayed trace (used on m68k)。

(3) long priority;
进程优先级:

Priority的值给出进程每次获取CPU后可使用的时间(按jiffies计)。优先级可通过系统调用sys_setpriorty改变(在kernel/sys.c中)。

(4) unsigned long rt_priority;
rt_priority给出实时进程的优先级:

rt_priority+1000给出进程每次获取CPU后可使用的时间(同样按jiffies计)。实时进程的优先级可通过系统调用sys_sched_setscheduler()改变(见kernel/sched.c)。

(5) long counter;

轮转法调度时表示进程当前还可运行多久。在进程开始运行是被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调度。重新调度将run_queue队列选出counter值最大的就绪进程并给予CPU使用权,因此counter起到了进程的动态优先级的作用(priority则是静态优先级)

(6) unsigned long policy;
该进程的进程调度策略:

可以通过系统调用sys_sched_setscheduler()更改(见kernel/sched.c)。
调度策略有:
·SCHED_OTHER 0 非实时进程,基于优先权轮转法(round robin)。
·SCHED_FIFO 1 实时进程,用先进先出算法。
·SCHED_RR 2 实时进程,用基于优先权的轮转法。

2. 信号处理

(1) unsigned long signal;
进程接收到的信号。每位表示一种信号,共32种。置位有效。
(2) unsigned long blocked;
进程所能接受信号的位掩码。置位表示屏蔽,复位表示不屏蔽。
(3) struct signal_struct *sig;
因为signal和blocked都是32位的变量,Linux最多只能接受32种信号。对每种信号,各进程可以由PCB的sig属性选择使用自定义的处理函数,或是系统的缺省处理函数。指派各种信息处理函数的结构定义在include/linux/sched.h中。对信号的检查安排在系统调用结束后,以及”慢速型”中断服务程序结束后(IRQ#_interrupt(),参见9.5节”启动内核”)。

3. 进程队列指针

(1) struct task_struct *next_task,*prev_task;
所有进程(以PCB的形式)组成一个双向链表。next_task和就是链表的前后指针。链表的头和尾都是init_task(即0号进程)。
(2) struct task_struct *next_run,*prev_run;
由正在运行或是可以运行的,其进程状态均为TASK_RUNNING的进程所组成的一个双向循环链表,即run_queue就绪队列。该链表的前后向指针用next_run和prev_run,链表的头和尾都是init_task(即0号进程)
(3) struct task_struct *p_opptr,*p_pptr;和struct task_struct *p_cptr,*p_ysptr,*p_osptr;
以上分别是指向原始父进程(original parent)、父进程(parent)、子进程(youngest child)及新老兄弟进程(younger sibling,older sibling)的指针。相关的操作
宏参见kenerl/linux/sched.h。

4. 进程标识

(1) unsigned short uid,gid;
uid和gid是运行进程的用户标识和用户组标识
(2) int groups[NGROUPS];
与多数现代UNIX操作系统一样,Linux允许进程同时拥有一组用户组号。在进程访问文件时,这些组号可用于合法性检查。
(3) unsigned short euid,egid;
euid和egid又称为有效的uid和gid。出于系统安全的权限的考虑,运行程序时要检查euid和egid的合法性。通常,uid等于euid,gid等于egid。有时候,系统会赋予一般用户暂时拥有root的uid和gid(作为用户进程的euid和egid),以便于进行运作。
(4) unsigned short fsuid,fsgid;
fsuid和fsgid称为文件系统的uid和gid,用于文件系统操作时的合法性检查,是Linux独特的标识类型。它们一般分别和euid和egid一致,但在NFS文件系统中NFS服务器需要作
为一个特殊的进程访问文件,这时只修改客户进程的fsuid和fsgid。
(5) unsigned short suid,sgid;
suid和sgid是根据POSIX标准引入的,在系统调用改变uid和gid时,用于保留真正的uid和gid。
(6) int pid,pgrp,session;
进程标识号、进程的组织号及session标识号,相关系统调用(见程序kernel/sys.c)有sys_setpgid、sys_getpgid、sys_setpgrp、sys_getpgrp、sys_getsid及sys_setsid几种。
(7) int leader;
是否是session的主管,布尔量

重点内容
5. 时间数据成员

(1) unsigned long timeout;
用于软件定时,指出进程间隔多久被重新唤醒。采用**tic**k为单位。

(2) unsigned long it_real_value,it_real_iner;
用于itimer(interval timer)软件定时。采用jiffies为单位,每个tick使it_real_value减到0时向进程发信号SIGALRM,并重新置初值。初值由it_real_incr保存。具体代码见kernel/itimer.c中的函数it_real_fn()。

(3) struct timer_list real_timer;
一种定时器结构(Linux共有两种定时器结构,另一种称作old_timer)。数据结构的定义在include/linux/timer.h中,相关操作函数见kernel/sched.c中add_timer()和del_timer()等。

(4) unsigned long it_virt_value,it_virt_incr;
关于进程用户态执行时间的itimer软件定时。采用jiffies为单位。进程在用户态运行时,每个tick使it_virt_value减1,减到0时向进程发信号SIGVTALRM,并重新置初值。初值由it_virt_incr保存。具体代码见kernel/sched.c中的函数do_it_virt()。

(5) unsigned long it_prof_value,it_prof_incr;
同样是itimer软件定时。采用jiffies为单位。不管进程在用户态或内核态运行,每个tick使it_prof_value减1,减到0时向进程发信号SIGPROF,并重新置初值。初值由it_prof_incr保存。 具体代码见kernel/sched.c中的函数do_it_prof。

(6) long utime,stime,cutime,cstime,start_time;
以上分别为进程在用户态的运行时间、进程在内核态的运行时间、所有层次子进程在用户态的运行时间总和、所有层次子进程在核心态的运行时间总和,以及创建该进程的时间。

6. 信号量数据成员

(1) struct sem_undo *semundo;
进程每操作一次信号量,都生成一个对此次操作的undo操作,它由sem_undo结构描述。
这些属于同一进程的undo操作组成的链表就由semundo属性指示。当进程异常终止时,系统会调用undo操作。sem_undo的成员semadj指向一个数据数组,表示各次undo的量。结构定义在include/linux/sem.h。

(2) struct sem_queue *semsleeping;
每一信号量集合对应一个sem_queue等待队列(见include/linux/sem.h)。进程因操作该信号量集合而阻塞时,它被挂到semsleeping指示的关于该信号量集合的sem_queue队列。反过来,semsleeping。sleeper指向该进程的PCB

7. 进程上下文环境

(1) struct desc_struct *ldt;
进程关于CPU段式存储管理局部描述符表指针,用于仿真WINE Windows的程序。其他情况下取值NULL,进程的ldt就是arch/i386/traps.c定义的default_ldt。

(2) struct thread_struct tss;
任务状态段,其内容与INTEL CPU的TSS对应,如各种通用寄存器。CPU调度时,当前运行进程的TSS保存到PCB的tss新选中进程的tss内容复制到CPU的TSS。结构定义在include/linux/tasks.h中。

(3) unsigned long saved_kernel_stack;
为MS-DOS的仿真程序(或叫系统调用vm86)保存的堆栈指针。

(4) unsigned long kernel_stack_page;
内核态运行时,每个进程都有一个内核堆栈,其基地址就保存在kernel_stack_page中。

8. 文件系统数据成员

(1) struct fs_struct *fs;
fs保存了进程本身与VFS的关系消息,其中root指向根目录结点,pwd指向当前目录结点,umask给出新建文件的访问模式(可由系统调用umask更改),count是Linux保留的属性。结构定义在include/linux/sched.h中。

(2) struct files_struct *files;
files包含了进程当前所打开的文件(struct file *fd[NR_OPEN])。在Linux中,一个进程最多只能同时打开NR_OPEN个文件。而且,前三项分别预先设置为标准输入、标准输出和出错消息输出文件。

(3) int link_count;
文件链(link)的数目。

9. 内存数据成员

(1) struct mm_struct *mm;
在linux中,采用按需分页的策略解决进程的内存需求。task_struct的数据成员mm指向关于存储管理的mm_struct结构。其中包含了一个虚存队列mmap,指向由若干vm_area_s
truct描述的虚存块。同时,为了加快访问速度,mm中的mmap_avl维护了一个AVL树。在树中,所有的vm_area_struct虚存块均由左指针指向相邻的低虚存块,右指针指向相邻的高虚存块,见下图。 结构定义在include/linux/sched.h中。

10. 页面管理

(1) int swappable:1;
进程占用的内存页面是否可换出swappable为1表示可换出。对该标志的复位和置位均在do_fork()函数中执行(见kerenl/fork.c)。

(2) unsigned long swap_address;
虚存地址比swap_address低的进程页面,以前已经换出或已换出过,进程下一次可换出的页面自swap_address开始。参见swap_out_process()和swap_out_pmd()(见mm/vmscan.c)。

(3) unsigned long min_flt,maj_flt;
该进程累计的minor缺页次数和major缺页次数。maj_flt基本与min_flt相同,但计数的范围比后者广(参见fs/buffer.c和mm/page_alloc.c)。min_flt只在do_no_page()、do_wp_page()里(见mm/memory.c)计数新增的可以写操作的页面。

(4) unsigned long nswap;
该进程累计换出的页面数。

(5) unsigned long cmin_flt,cmaj_flt,cnswap;
本进程作为祖先的所有层次子进程的累计换入页面、换出页面计数。

(6) unsigned long old_maj_flt,dec_flt;

(7) unsigned long swap_cnt;
下一次信号最多可换出的页数。

11. 支持对称多处理器方式(SMP)时的数据成员

(1) int processor;
进程正在使用的CPU。
(2) int last_processor;
进程最后一次使用的CPU。
(3) int lock_depth;
上下文切换时系统内核锁的深度。

12. 其它数据成员

(1) unsigned short used_math;
是否使用FPU。
(2) char comm[16];
进程正在运行的可执行文件的文件名
(3) struct rlimit rlim[RLIM_NLIMITS];
结构rlimit用于资源管理,定义在linux/include/linux/resource.h中,成员共有两项:
rlim_cur是资源的当前最大数目;rlim_max是资源可有的最大数目。在i386环境中,受控资源共有RLIM_NLIMITS项,即10项,定义在linux/include/asm/resource.h中。
(4) int errno;
最后一次出错的系统调用的错误号,0表示无错误。系统调用返回时,全程量也拥有该错误号。
(5) long debugreg[8];
保存INTEL CPU调试寄存器的值,在ptrace系统调用中使用。
(6) struct exec_domain *exec_domain;
Linux可以运行由80386平台其它UNIX操作系统生成的符合iBCS2标准的程序。关于此类程序与Linux程序差异的消息就由exec_domain结构保存。
(7) unsigned long personality;
Linux可以运行由80386平台其它UNIX操作系统生成的符合iBCS2标准的程序。 Personality进一步描述进程执行的程序属于何种UNIX平台的”个性”信息。通常有PER_Linux、PER_Linux_32BIT、PER_Linux_EM86、PER_SVR3、PER_SCOSVR3、PER_WYSEV386、PER_ISCR4、PER_BSD、PER_XENIX和PER_MASK等,参见include/linux/personality.h。
(8) struct linux_binfmt *binfmt;
指向进程所属的全局执行文件格式结构,共有a。out、script、elf和java等四种。结构定义在include/linux/binfmts.h中(core_dump、load_shlib(fd)、load_binary、use_count)。
(9) int exit_code,exit_signal;
引起进程退出的返回代码exit_code,引起错误的信号名exit_signal。
(10) int dumpable:1;
布尔量,表示出错时是否可以进行memory dump。
(11) int did_exec:1;
按POSIX要求设计的布尔量,区分进程是正在执行老程序代码,还是在执行execve装入的新代码。
(12) int tty_old_pgrp;
进程显示终端所在的组标识。
(13) struct tty_struct *tty;
指向进程所在的显示终端的信息。如果进程不需要显示终端,如0号进程,则该指针为空。结构定义在include/linux/tty.h中。
(14) struct wait_queue *wait_chldexit;
在进程结束时,或发出系统调用wait4后,为了等待子进程的结束,而将自己(父进程)睡眠在该队列上。结构定义在include/linux/wait.h中。

13. 进程队列的全局变量
(1) current;
当前正在运行的进程的指针,在SMP中则指向CPU组中正被调度的CPU的当前进程:
#define current(0+current_set[smp_processor_id()])/sched.h/
struct task_struct *current_set[NR_CPUS];
(2) struct task_struct init_task;
即0号进程的PCB,是进程的”根”,始终保持初值INIT_TASK。
(3) struct task_struct *task[NR_TASKS];
进程队列数组,规定系统可同时运行的最大进程数(见kernel/sched.c)。NR_TASKS定义
在include/linux/tasks.h中,值为512。每个进程占一个数组元素(元素的下标不一定就
是进程的pid),task[0]必须指向init_task(0号进程)。可以通过task[]数组遍历所有进
程的PCB。但Linux也提供一个宏定义for_each_task()(见include/linux/sched.h),它
通过next_task遍历所有进程的PCB:
#define for_each_task(p) \
for(p=&init_task;(p=p->next_task)!=&init_task;)
(4) unsigned long volatile jiffies;
Linux的基准时间(见kernal/sched.c)。系统初始化时清0,以后每隔10ms由时钟中断服
务程序do_timer()增1。
(5) int need_resched;
重新调度标志位(见kernal/sched.c)。当需要Linux调度时置位。在系统调用返回前(或
者其它情形下),判断该标志是否置位。置位的话,马上调用schedule进行CPU调度。
(6) unsigned long intr_count;
记录中断服务程序的嵌套层数(见kernal/softirq.c)。正常运行时,intr_count为0。当
处理硬件中断、执行任务队列中的任务或者执行bottom half队列中的任务时,intr_co
unt非0。这时,内核禁止某些操作,例如不允许重新调度。
3.1.3调度和时间片
对CPU访问的裁决过程被称为调度(Scheduling)。良好的调度决策要尊重用户赋予的优
先级,这可以建立一种所有进程都在同时运行的十分逼真的假象。糟糕的调度决策会使
操作系统变得沉闷缓慢。这是Linux调度程序必须经过高度优化的一个原因。
从概念上来说,调度程序把时间分为小片断,并根据一定的原则把这些片断分配给进程
。时间的这些小片断称为时间片。
3.1.4实时进程与非实时进程
Linux把执行的任务比较紧迫的进程定义为实时进程,这些进程通常会比一般进程先得到
机会运行。根据它们的调度策略(后面还会讲到)实时进程分为两种:一种执行的任务
比较短小,因此采用先进先出的策略,即当前进程执行完才给其它进程运行的机会;另
一种则采用时间片轮转法,让这样的实时进程周期性地交替运行。
非实时进程是相对实时进程而言的,Linux系统把执行的任务不怎么紧迫的进程统称为非
实时进程。Linux通常对这类进程采用优先权算法进行调度,即定期检查进程的优先级,优
先级高的进程具有较高的优先权,将会优先获得CPU时间而运行。
3.1.5 Linux进程优先级
优先级是一些简单的整数,它代表了为决定应该允许哪一个进程使用CPU的资源时判断方
便而赋予进程的权值–优先级越高,它得到CPU时间的机会也就越大。
在Linux中,非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时
进程又增加了第三种优先级,实时优先级。
1. 静态优先级(priority)–被称为”静态”是因为它不随时间而改变,只能由用户进行
修改。它指明了在被迫和其它进程竞争CPU之前该进程所应该被允许的时间片的最大值(
20)。
2. 动态优先级(counter)–counter 即系统为每个进程运行而分配的时间片,Linux兼
用它来表示进程的动态优先级。只要进程拥有CPU,它就随着时间不断减小;当它为0时
,标记进程重新调度。它指明了在当前时间片中所剩余的时间量(最初为20)。
3. 实时优先级(rt_priority)–值为1000。Linux把实时优先级与counter值相加作为实
时进程的优先权值。较高权值的进程总是优先于较低权值的进程,如果一个进程不是实
时进程,其优先权就远小于1000,所以实时进程总是优先。
3.1.6 Linux进程系统的特点
Linux是一个多进程系统,它具有以下特点:
1. 并行化
一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员的大脑中早就形
成了这种概念, 首先将问题分解成一个个小问题, 将小问题再细分, 最后在一个合适的
规模上做成一个函数。 在软件工程中也是这么说的。如果我们以图的方式来思考, 一些
小问题的计算是可以互不干扰的, 可以同时处理, 而在关键点则需要统一在一个地方来
处理, 这样程序的运行就是并行的, 至少从人的时间观念上来说是这样的。 而每个小问
题的计算又是较简单的。
2. 简单有序
程序员为每个进程设计好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起
, 对每个进程的设计是简单的, 只在总控部分小心应付, 就可完成整个程序的施工。
3. 互不干扰
这个特点是操作系统的特点, 各个进程是独立的, 不会串位。
4. 事务化
比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次查询即可, 即完成
一个事务。当电话查询开始时, 产生这样一个进程对付这次查询; 另一个电话进来时,
主控程序又产生一个这样的进程对付, 每个进程完成查询任务后消失. 这样的编程多简
单, 只要做一次查询的程序就可以了。
一个多进程的操作系统,进程是分离的任务,拥有各自的权利和责任。如果一个进
程崩溃,它不应该让系统的另一个进程崩溃。每一个独立的进程运行在自己的虚拟地址
空间,除了通过安全的核心管理的机制之外无法影响其他的进程。
在一个进程的生命周期中,进程会使用许多系统资源。比如利用系统的CPU执行它的
指令,用系统的物理内存来存储它和它的数据。它会打开和使用文件系统中的文件,会
直接或者间接使用系统的物理设备。如果一个进程独占了系统的大部分物理内存和CPU,
对于其他进程就是不公平的。所以Linux必须跟踪进程本身和它使用的系统资源以便公平
地管理系统中的进程。
系统最宝贵的资源就是CPU。通常系统只有一个CPU。Linux作为一个多进程的操作系
统,它的目标就是让进程在系统的CPU上运行,充分利用CPU。如果进程数多于CPU(一般
情况都是这样),其他的进程就必须等到CPU被释放才能运行。多进程的思想就是:一个
进程一直运行,直到它必须等待,通常是等待一些系统资源(也包括时间片),等拥有了
资源,它才可以继续运行。在一个单进程的系统中,比如DOS,CPU被简单地设为空闲,
这样等待资源的时间就会被浪费。而在一个多进程的系统中,同一时刻许多进程在内存
中,当一个进程必须等待时,操作系统将CPU从这个进程切换到另一个更需要的进程。
在Linux中,每个进程用一个task_struct的结构来表示,用来管理系统中的进程。
Task向量表是指向系统中每一个task_struct结构的指针的数组。这意味着系统中的最大
进程数受到Task向量表的限制,缺省是512。这个表让Linux可以查到系统中的所有的进
程。操作系统初始化后,建立了第一个task_struct结构INIT_TASK。当新的进程创建时
,从系统内存中分配一个新的task_struct,并增加到Task向量表中。为了更容易查找,
用current指针指向当前运行的进程。
task_struct结构中有关于进程调度的两个重要的数据项:
struct task_struct {
………….
volatile long state; /* -1 unrunnable , 0 runnable , >0 stopped */
unsigned long flags; /* per process flags, defined below */
………….
};
每个在Task向量表中登记的进程都有相应的进程状态和进程标志,它们是进行进程
调度的重要依据。进程在执行了相应的进程调度操作后,会由于某些原因改变自身的状
态和标志,也就是改变state和flags这两个数据项。进程的状态不同、标志位不同对应
了进程可以执行不同操作。
Linux在sched.h中定义了六种状态,十一种标志(参见3.1.2task_struct结构描述):


#define TASK_RUNNING          0
#define TASK_INTERRUPTIBLE    1
#define TASK_UNINTERRUPTIBLE  2
#define TASK_ZOMBIE           4
#define TASK_STOPPED          8
#define TASK_SWAPPING         16

它们的含义分别是:
1. TASK_RUNNING:正在运行的进程(是系统的当前进程)或准备运行的进程(在Runni
ng队列中,等待被安排到系统的CPU)。处于该状态的进程实际参与了进程调度。
2. TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程
被信号中断、唤醒后进入就绪状态。
3. TASK_UNINTERRUPTIBLE:处于等待队列中的进程,直接等待硬件条件,待资源有效时
唤醒,不可由其它进程通过信号中断、唤醒。
4. TASK_ZOMBIE:终止的进程,是进程结束运行前的一个过度状态(僵死状态)。虽然
此时已经释放了内存、文件等资源,但是在Task向量表中仍有一个task_struct数据结构
项。它不进行任何调度或状态转换,等待父进程将它彻底释放。
5. TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。正在调试的进程可以在
该停止状态。
6. TASK_SWAPPING:进程页面被兑换出内存的进程。这个状态基本上没有用到,只有在
sched.c的count_active_tasks()函数中判断处于该种状态的进程也属于active的进程
,但没有对该状态的赋值。
进程的状态随着进程的调度发生改变,下图显示了一个进程的状态转换关系:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值