概述
进程在操作系统中都有一个户口,用于表示这个进程。这个户口操作系统被称为PCB(进程控制块),在linux中具体实现是 task_struct
数据结构
,/usr/src/linux-headers-4.4.0-96/include/linux/sched.h文件中可以查看struct task_struct 结构体定义(系统版本可能不同linux-headers-4.4.0-96会有一定差别)。其内部成员有很多,我们重点掌握以下部分即可:
-
进程
id
。系统中每个进程有唯一的
id
,在
C
语言中用
pid_t
类型表示,其实就是一个非负整数。
-
进程的状态,有就绪、运行、挂起、停止等状态。
-
进程切换时需要保存和恢复的一些
CPU
寄存器。
-
描述虚拟地址空间的信息。
-
描述控制终端的信息。
-
当前工作目录(
Current Working Directory
)。
-
getcwd –pwd
-
-
umask
掩码。
-
文件描述符表,包含很多指向
file
结构体的指针。
-
和信号相关的信息。
-
用户
id
和组
id
。
-
会话(
Session
)和进程组。
-
进程可以使用的资源上限(
Resource Limit
)。
-
ulimit -a
-
struct task_struct {
/*
* offsets of these are hardcoded elsewhere - touch with care
*/
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;
volatile long need_resched; //与进程调度有关表示用户从系统空间按返回用户空间要执行的一次调度
unsigned long ptrace;
int lock_depth; /* Lock depth */
/*
* offset 32 begins here on 32-bit platforms. We keep
* all fields in a single cacheline that are needed for
* the goodness() loop in schedule().
*/
long counter; //与进程调度相关
long nice;
unsigned long policy; //实用于本进程的调度政策
struct mm_struct *mm;
int processor;
/*
* cpus_runnable is ~0 if the process is not running on any
* CPU. It's (1 << cpu) if it's running on a CPU. This mask
* is updated under the runqueue lock.
*
* To determine whether a process might run on a CPU, this
* mask is AND-ed with cpus_allowed.
*/
unsigned long cpus_runnable, cpus_allowed;
/*
* (only the 'next' pointer fits into the cacheline, but
* that's just fine.)
*/
struct list_head run_list;
unsigned long sleep_time;
struct task_struct *next_task, *prev_task; //内核会对每一个进程做点什么事情的时候,常常需要将其连成一个队列,这2个指针用于这个目的
struct mm_struct *active_mm;
struct list_head local_pages;
unsigned int allocation_order, nr_local_pages;
/* 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 did_exec:1;
unsigned task_dumpable:1;
pid_t pid; //进程号
pid_t pgrp;
pid_t tty_old_pgrp;
pid_t session;
pid_t tgid;
/* 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; //用于族谱信息的,例如p_opptr指向父进程
struct list_head thread_group;
/* PID hash table linkage. */
struct task_struct *pidhash_next;
struct task_struct **pidhash_pprev; //pid是随机分配的,我们常常使用kill pid想进程发送信号(大部分人认为是杀死进程,其实这是个发送信号的指令,默认的参数为杀死。如果想暂停某进程,只需kill STOP 进程的PID),这里可以看到根据pid寻找进程的操作是经常被使用的,而pid又是随机分配,于是这里边用这2个指针指向一个杂凑数组,数组是按照杂凑的算法,以pid为关键字建立,方便根据pid来寻找task_struct
wait_queue_head_t wait_chldexit; /* for wait4() */
struct completion *vfork_done; /* for vfork() */
unsigned long 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 or 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; //权限,比如该进程是否有权限从新引导系统,这里是大概介绍
int keep_capabilities:1;
struct user_struct *user; //指向该进程拥有的用户
/* limits */
struct rlimit rlim[RLIM_NLIMITS]; //进程对各种资源使用数量的限制,详细见下
unsigned short used_math;
char comm[16];
/* file system info */
int link_count, total_link_count;
struct tty_struct *tty; /* NULL if no tty */
unsigned int locks; /* How many file locks are being held */
/* 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;
/* namespace */
struct namespace *namespace;
/* signal handlers */
spinlock_t sigmask_lock; /* Protects signal and blocked */
struct signal_struct *sig;
sigset_t blocked;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty */
spinlock_t alloc_lock;
/* journalling filesystem info */
void *journal_info;
};
#define TASK_RUNNING 0 //不是表示正在运行,而是表示可以被调用
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 4
#define TASK_STOPPED 8 //对应于task_struct中的state,进程运行状态
//对应task_struct的flag
#define PF_ALIGNWARN 0x00000001 /* Print alignment warning msgs */
/* Not implemented yet, only for 486*/
#define PF_STARTING 0x00000002 /* being created */
#define PF_EXITING 0x00000004 /* getting shut down */
#define PF_FORKNOEXEC 0x00000040 /* forked but didn't exec */
#define PF_SUPERPRIV 0x00000100 /* used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* dumped core */
#define PF_SIGNALED 0x00000400 /* killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_MEMDIE 0x00001000 /* Killed for out-of-memory */
#define PF_FREE_PAGES 0x00002000 /* per process page freeing */
#define PF_NOIO 0x00004000 /* avoid generating further I/O */
#define PF_FSTRANS 0x00008000 /* inside a filesystem transaction */
#define PF_USEDFPU 0x00100000 /* task used FPU this quantum (SMP) */
//进程的个性化信息
enum {
MMAP_PAGE_ZERO = 0x0100000,
ADDR_LIMIT_32BIT = 0x0800000,
SHORT_INODE = 0x1000000,
WHOLE_SECONDS = 0x2000000,
STICKY_TIMEOUTS = 0x4000000,
ADDR_LIMIT_3GB = 0x8000000,
};
/*
* Personality types.
*
* These go in the low byte. Avoid using the top bit, it will
* conflict with error returns.
*/
enum {
PER_LINUX = 0x0000,
PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT,
PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE,
PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS |
WHOLE_SECONDS | SHORT_INODE,
PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS,
PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE,
PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS,
PER_BSD = 0x0006,
PER_SUNOS = 0x0006 | STICKY_TIMEOUTS,
PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE,
PER_LINUX32 = 0x0008,
PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB,
PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,/* IRIX5 32-bit */
PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,/* IRIX6 new 32-bit */
PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,/* IRIX6 64-bit */
PER_RISCOS = 0x000c,
PER_SOLARIS = 0x000d | STICKY_TIMEOUTS,
PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_HPUX = 0x000f,
PER_OSF4 = 0x0010, /* OSF/1 v4 */
PER_MASK = 0x00ff,
};
//进程资源的限制,对应task_struct中的struct rlimit rlim[RLIM_NLIMITS],RLIM_NLIMITS的值是11,代表11项资源,分别是
#define RLIMIT_CPU 0 /* CPU time in ms */
#define RLIMIT_FSIZE 1 /* Maximum filesize */
#define RLIMIT_DATA 2 /* max data size */
#define RLIMIT_STACK 3 /* max stack size */
#define RLIMIT_CORE 4 /* max core file size */
#define RLIMIT_RSS 5 /* max resident set size */
#define RLIMIT_NPROC 6 /* max number of processes */
#define RLIMIT_NOFILE 7 /* max number of open files */
#define RLIMIT_MEMLOCK 8 /* max locked-in-memory address space */
#define RLIMIT_AS 9 /* address space limit */
#define RLIMIT_LOCKS 10 /* maximum file locks held */
task_struct结构描述
1. 进程状态(State)
进程执行时,它会根据具体情况改变状态。进程状态是调度和对换的依据。Linux中的进程主要有如下状态,如表4.1所示。
表4.1 Linux进程的状态
内核表示 |
含义 |
TASK_RUNNING |
可运行 |
TASK_INTERRUPTIBLE |
可中断的等待状态 |
TASK_UNINTERRUPTIBLE |
不可中断的等待状态 |
TASK_ZOMBIE |
僵死 |
TASK_STOPPED |
暂停 |
TASK_SWAPPING |
换入/换出 |
·可运行状态
处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序决定。
·等待状态
处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态和不可中断的等待状态。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。
·暂停状态
此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。
·僵死状态
进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。
2. 进程调度信息
调度程序利用这部分信息决定系统中哪个进程最应该运行,并结合进程的状态信息保证系统运转的公平和高效。这一部分信息通常包括进程的类别(普通进程还是实时进程)、进程的优先级等等。如表4.2所示:
表4.2 进程调度信息
域名 |
含义 |
need_resched |
调度标志 |
Nice |
静态优先级 |
Counter |
动态优先级 |
Policy |
调度策略 |
rt_priority |
实时优先级 |
在下一章的进程调度中我们会看到,当need_resched被设置时,在“下一次的调度机会”就调用调度程序schedule()。 counter代表进程剩余的时间片,是进程调度的主要依据,也可以说是进程的动态优先级,因为这个值在不断地减少;nice是进程的静态优先级,同时也代表进程的时间片,用于对counter赋值,可以用nice()系统调用改变这个值;policy是适用于该进程的调度策略,实时进程和普通进程的调度策略是不同的;rt_priority只对实时进程有意义,它是实时进程调度的依据。
进程的调度策略有三种,如表4.3所示。
表4.3 进程调度的策略
名称 |
解释 |
适用范围 |
SCHED_OTHER |
其他调度 |
普通进程 |
SCHED_FIFO |
先来先服务调度 |
实时进程 |
SCHED_RR |
时间片轮转调度 |
只有root用户能通过sched_setscheduler()系统调用来改变调度策略。
3 .标识符(Identifiers)
每个进程有进程标识符、用户标识符、组标识符,如表4.4所示。
不管对内核还是普通用户来说,怎么用一种简单的方式识别不同的进程呢?这就引入了进程标识符(PID:process identifier),每个进程都有一个唯一的标识符,内核通过这个标识符来识别不同的进程,同时,进程标识符PID也是内核提供给用户程序的接口,用户程序通过PID对进程发号施令。PID是32位的无符号整数,它被顺序编号:新创建进程的PID通常是前一个进程的PID加1。然而,为了与16位硬件平台的传统Linux系统保持兼容,在Linux上允许的最大PID号是32767,当内核在系统中创建第32768个进程时,就必须重新开始使用已闲置的PID号。
表4.4 各种标识符
域名 |
含义 |
Pid |
进程标识符 |
Uid、gid |
用户标识符、组标识符 |
Euid、egid |
有效用户标识符、有效组标识符 |
Suid、sgid |
备份用户标识符、备份组标识符 |
Fsuid、fsgid |
文件系统用户标识符、文件系统组标识符 |
另外,每个进程都属于某个用户组。task_struct结构中定义有用户标识符和组标识符。它们同样是简单的数字,这两种标识符用于系统的安全控制。系统通过这两种标识符控制进程对系统中文件和设备的访问,其它几个标识符将在文件系统中讨论。
4. 进程通信有关信息(IPC:Inter_Process Communication)
为了使进程能在同一项任务上协调工作,进程之间必须能进行通信即交流数据。
Linux支持多种不同形式的通信机制。它支持典型的Unix通信机制(IPC Mechanisms):信号(Signals)、管道(Pipes),也支持System V 通信机制:共享内存(Shared Memory)、信号量和消息队列(Message Queues),如表4.5。
表4.5 进程通信有关信息
域名 |
含义 |
Spinlock_t sigmask_lock |
信号掩码的自旋锁 |
Long blocked |
信号掩码 |
|
信号处理函数 |
Struct sem_undo *semundo |
为避免死锁而在信号量上设置的取消操作 |
Struct sem_queue *semsleeping |
与信号量操作相关的等待队列 |
这些域的具体含义将在进程通信一章进行讨论。
5. 进程链接信息(Links)
程序创建的进程具有父/子关系。因为一个进程能创建几个子进程,而子进程之间有兄弟关系,在task_struct结构中有几个域来表示这种关系。
在Linux系统中,除了初始化进程init,其他进程都有一个父进程(parent process)或称为双亲进程。可以通过fork()或clone()系统调用来创建子进程,除了进程标识符(PID)等必要的信息外,子进程的task_struct结构中的绝大部分的信息都是从父进程中拷贝,或说“克隆”过来的。系统有必要记录这种“亲属”关系,使进程之间的协作更加方便,例如父进程给子进程发送杀死(kill)信号、父子进程通信等,就可以用这种关系很方便地实现。
每个进程的task_struct结构有许多指针,通过这些指针,系统中所有进程的task_struct结构就构成了一棵进程树,这棵进程树的根就是初始化进程init的task_struct结构(init进程是Linux内核建立起来后人为创建的一个进程,是所有进程的祖先进程)。表4.6是进程所有的链接信息。
表4.6 进程链接信息
名称 |
英文解释 |
中文解释 [指向哪个进程] |
p_opptr |
Original parent |
祖先 |
p_pptr |
Parent |
父进程 |
p_cptr |
Child |
子进程 |
p_ysptr |
Younger sibling |
弟进程 |
p_osptr |
Older sibling |
兄进程 |
Pidhash_next、 Pidhash_pprev |
进程在哈希表中的链接 |
|
Next_task、 prev_task |
进程在双向循环链表中的链接 |
|
Run_list |
运行队列的链表 |
6. 时间和定时器信息(Times and Timers)
一个进程从创建到终止叫做该进程的生存期(lifetime)。进程在其生存期内使用CPU的时间,内核都要进行记录,以便进行统计、计费等有关操作。进程耗费CPU的时间由两部分组成:一是在用户模式(或称为用户态)下耗费的时间、一是在系统模式(或称为系统态)下耗费的时间。每个
时钟滴答
,也就是每个时钟中断,内核都要更新当前进程耗费CPU的时间信息。
“时间”对操作系统是极其重要的。读者可能了解计算机时间的有关知识,例如8353/8254这些物理器件,INT 08、INT 1C等时钟中断等,可能有过编程序时截获时钟中断的成就感,不管怎样,下一章我们将用较大的篇幅尽可能向读者解释清楚操作系统怎样建立完整的时间机制、并在这种机制的激励下进行调度等活动。
建立了“时间”的概念,“定时”就是轻而易举的了,无非是判断系统时间是否到达某个时刻,然后执行相关的操作而已。Linux提供了许多种定时方式,用户可以灵活使用这些方式来为自己的程序定时。
表4.7是和时间有关的域,上面所说的counter是指进程剩余的CPU时间片,也和时间有关,所以这里我们再次提及它。表4.8是进程的所有定时器。
表4.7 与时间有关的域
域名 |
含义 |
Start_time |
进程创建时间 |
Per_cpu_utime |
进程在某个CPU上运行时在用户态下耗费的时间 |
Per_cpu_stime |
进程在某个CPU上运行时在系统态下耗费的时间 |
Counter |
进程剩余的时间片 |
表4.8 进程的所有定时器
定时器类型 |
解释 |
什么时候更新 |
用来表示此种定时器的域 |
ITIMER_REAL |
实时定时器 |
实时更新,即不论该进程是否运行 |
it_real_value |
it_real_incr |
|||
real_timer |
|||
ITIMER_VIRTUAL |
虚拟定时器 |
只在进程运行于用户态时更新 |
it_virt_value |
it_virt_incr |
|||
ITIMER_PROF |
概况定时器 |
进程运行于用户态和系统态时更新 |
it_prof_value |
it_prof_incr |
进程有三种类型的定时器:实时定时器、虚拟定时器和概况定时器。这三种定时器的特征共有三个:到期时间、定时间隔、要触发的事件。到期时间就是定时器到什么时候完成定时操作,从而触发相应的事件;定时间隔就是两次定时操作的时间间隔,它决定了定时操作是否继续进行,如果定时间隔大于0,则在定时器到期时,该定时器的到期时间被重新赋值,使定时操作继续进行下去,直到进程结束或停止使用定时器,只不过对不同的定时器,到期时间的重新赋值操作是不同的。在表4.8中,每个定时器都有两个域来表示到期时间和定时间隔:value和incr,二者的单位都是时钟滴答,和jiffies的单位是一致的,Linux所有的时间应用都建立在jiffies之上。虚拟定时器和概况定时器到期时由内核发送相应的信号,而实时定时器比较特殊,它由内核机制提供支持,我们将在后面讨论这个问题。
每个时钟中断,当前进程所有和时间有关的信息都要更新:当前进程耗费的CPU时间要更新,以便于最后的计费;时间片计数器counter要更新,如果counter<=0,则要执行调度程序;进程申请的延时要更新,如果延时时间到了,则唤醒该进程;所有的定时器都要更新,Linux内核检测这些定时器是否到期,如果到期,则执行相应的操作。在这里,“更新”的具体操作是不同的:对counter,内核要对它减值,而对于所有的定时器,就是检测它的值,内核把系统当前时间和其到期时间作一比较,如果到期时间小于系统时间,则表示该定时器到期。但为了方便,我们把这些操作一概称为“更新”,请读者注意。
请特别注意上面三个定时器的更新时间。实时定时器不管其所属的进程是否运行都要更新,所以,时钟中断来临时,系统中所有进程的实时定时器都被更新,如果有多个进程的实时定时器到期,则内核要一一处理这些定时器所触发的事件。而虚拟定时器和概况定时器只在进程运行时更新,所以,时钟中断来临时,只有当前进程的概况定时器得到更新,如果当前进程运行于用户态,则其虚拟定时器也得到更新。
此外,Linux内核对这三种定时器的处理是不同的,虚拟定时器和概况定时器到期时,内核向当前进程发送相应的信号:SIGVTALRM 、SIGPROF ;而实时定时器要执行的操作由real_timer决定,real_time是timer_list类型的变量(定义:struct timer_list real_timer),其中容纳了实时定时器的到期时间、定时间隔等信息,我们将在下一章详细讨论这些内容。
7. 文件系统信息(File System)
进程可以打开或关闭文件,文件属于系统资源,Linux内核要对进程使用文件的情况进行记录。task_struct结构中有两个数据结构用于描述进程与文件相关的信息。其中,fs_struct中描述了两个VFS索引节点(VFS inode),这两个索引节点叫做root和pwd,分别指向进程的可执行映象所对应的根目录(home directory)和当前目录或工作目录。file_struct结构用来记录了进程打开的文件的描述符(descriptor)。如表4.9所示。
表4.9 与文件系统相关的域
定义形式 |
解释 |
Sruct fs_struct *fs |
进程的可执行映象所在的文件系统 |
Struct files_struct *files |
进程打开的文件 |
在文件系统中,每个VFS索引节点唯一描述一个文件或目录,同时该节点也是向更低层的文件系统提供的统一的接口。
8. 虚拟内存信息(Virtual Memory)
除了内核线程(kernel thread),每个进程都拥有自己的地址空间(也叫虚拟空间),用mm_struct来描述。另外Linux2.4还引入了另外一个域active_mm,这是为内核线程而引入。因为内核线程没有自己的地址空间,为了让内核线程与普通进程具有统一的上下文切换方式,当内核线程进行上下文切换时,让切换进来的线程的active_mm指向刚被调度出去的进程的active_mm(如果进程的mm域不为空,则其active_mm域与mm域相同)。内存信息如表4.10所示。
表4.10 虚拟内存描述信息
定义形式 |
解释 |
Struct mm_struct *mm |
描述进程的地址空间 |
Struct mm_struct *active_mm |
内核线程所借用的地址空间 |
9.页面管理信息
当物理内存不足时,Linux内存管理子系统需要把内存中的部分页面交换到外存,其交换是以页为单位的。有关页面的描述信息如表4.11。
表4.11 页面管理信息
定义形式 |
解释 |
Int swappable |
进程占用的内存页面是否可换出 |
Unsigned long min_flat,maj_flt,nswap |
进程累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数 |
Unsigned long cmin_flat,cmaj_flt,cnswap |
本进程作为祖先进程,其所有层次子进程的累计的次(minor)缺页次数、主(major)次数及累计换出、换入页面数 |
10.对称多处理机(SMP)信息
Linux2.4对SMP进行了全面的支持,表4.12是与多处理机相关的几个域。
表4.12 与多处理机相关的域
定义形式 |
解释 |
Int has_cpu |
进程是否当前拥有CPU |
Int processor |
进程当前正在使用的CPU |
Int lock_depth |
上下文切换时内核锁的深度 |
11.
和处理器相关的环境(上下文)信息(Processor Specific Context
)
这里要特别注意标题:和“
处理器
”相关的环境信息。进程作为一个执行环境的综合,当系统调度某个进程执行,即为该进程建立完整的环境时,处理器(processor)的寄存器、堆栈等是必不可少的。因为不同的处理器对内部寄存器和堆栈的定义不尽相同,所以叫做“和处理器相关的环境”,也叫做“
处理机状态
”。当进程暂时停止运行时,处理机状态必须保存在进程的task_struct结构中,当进程被调度重新运行时再从中恢复这些环境,也就是恢复这些寄存器和堆栈的值。处理机信息如表4.13所示。
表4.13 与处理机相关的信息
定义形式 |
解释 |
Struct thread_struct *tss |
任务切换状态 |
12.其它
(1) struct wait_queue *wait_chldexit
在进程结束时,或发出系统调用wait4时,为了等待子进程的结束,而将自己(父进程)睡眠在该等待队列上,设置状态标志为TASK_INTERRUPTIBLE,并且把控制权转给调度程序。
(2)Struct rlimit rlim[RLIM_NLIMITS];
每一个进程可以通过系统调用setlimit和getlimit来限制它资源的使用。
(3)Int exit_code exit_signal;
程序的返回代码以及程序异常终止产生的信号,这些数据由父进程(子进程完成后) 轮流查询。
(4)Char comm[16]
这个域存储进程执行的程序的名字,这个名字用在调试中。
(5)Unsigned long personality;
Linux可以运行X86平台上其它Unix操作系统生成的符合iBCS2标准的程序, personality进一步描述进程执行的程序属于何种Unix平台的“个性”信息。通常有PER_Linux,PER_Linux_32BIT,PER_Linux_EM86,PER_SVR4,PER_SVR3,PER_SCOSVR3,PER_WYSEV386,PER_ISCR4,PER_BSD,PER_XENIX和PER_MASK等,参见include/Linux/personality.h>。
(6) int did_exec:1;
按POSIX要求设计的布尔量,区分进程正在执行老程序代码,还是用系统调用execve()装入一个新的程序。
(7)struct linux_binfmt *binfmt
指向进程所属的全局执行文件格式结构,共有a.out、script、elf、java等四种。
综上所述,我们对进程的task_struct结构进行了归类讨论,还有一些域没有涉及到,在第六章进程的创建与执行一节几乎涉及到所有的域,在那里可以对很多域有更深入一步的理解。task_struct结构是进程实体的核心,Linux内核通过该结构来控制进程:首先通过其中的调度信息决定该进程是否运行;当该进程运行时,根据其中保存的处理机状态信息来恢复进程运行现场,然后根据虚拟内存信息,找到程序的正文和数据;通过其中的通信信息和其他进程实现同步、通信等合作。几乎所有的操作都要依赖该结构,所以,task_struct结构是一个进程存在的唯一标志。