2、linux进程相关的数据结构
首先来看内核描述进程所用的数据结构。在Linux内核0.11版本中,进程数据结构的定义在sched.h文件中,定义如下
struct
task_struct {
/* these are hardcoded - don't touch */
long
state;
/*表示进程当前的状态的字段。关于进程状态字段相关值的定义也在shced.h文件中*/
long
counter; /*进程已用的时间片计数器*/
long
priority; /*进程优先级*/
long
signal; /*进程接收到的信号量*/
struct
sigaction sigaction[32]; /*sigaction与进程信号量的处理有关*/
long
blocked;
/* bitmap of masked signals */
/* various fields */
int
exit_code;
unsigned
long
start_code,end_code,end_data,brk,start_stack;
long
pid,father,pgrp,session,leader;
unsigned
short
uid,euid,suid;
unsigned
short
gid,egid,sgid;
long
alarm;
long
utime,stime,cutime,cstime,start_time;
unsigned
short
used_math;
/* file system info */
int
tty;
/* -1 if no tty, so it must be signed */
unsigned
short
umask;
struct
m_inode * pwd;
struct
m_inode * root;
struct
m_inode * executable;
unsigned
long
close_on_exec;
struct
file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct
desc_struct ldt[3];
/* tss for this task */
struct
tss_struct tss;
};
上面对task_struct的定义,又涉及到许多其它的数据结构,这里暂时就不一一列举,随着后面讨论的深入,会逐渐在相关的部分列出来
3、Linux进程状态state可选值有哪些与对state字段的修改
参见笔记“Linux进程状态变化详解”
4、进程的时间片计数器counter的初始值设定和修改
对进程couter字段初始值的设定是在fork.c中,但创建一个进程的时候,需要设定它的couter的初始值。couter的初始值就是进程的优先级数。具体的表示代码为(fork.c的copy_process函数中,这个函数是创建子进程赋值父进程的数据时使用)
p->counter = p->priority;具体的进程的初始化将在后面讨论。对counter的修改是在两个地方,一个是在sched.c的schedule函数中,这里的修改主要是在进行进程调度时找不到合适的进程去调度(要么处于睡眠,要么counter值已经为0了),就把所有进程的counter的值都进行修改,这个地方修改counter的代码如下
for
(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if
(*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
还有一个地方是在处理中断函数中do_timer里面,将当前进程的时间片减1。代码如下
if
((--current->counter)>0)
return
;
current->counter=0;
5、进程优先级的初始值与优先级调整
进程的优先级的初始值是设置与其父进程一样,在fork.c中copy_process中,有这么一句*p=*current;然后,在后面剩下的部分,没有对p的priority字段做单独的设置,所以一个进程的优先级是与创建这个进程的父进程的优先级是一样的。
对进程优先级做修改的一个函数是sched.c的sys_nice,这个函数的定义如下
int
sys_nice(
long
increment)
{
if
(current->priority-increment>0)
current->priority -= increment;
return
0;
}
从函数的定义来看,这个函数的作用就是根据传入的值,来对进程的优先级进行调整,是进程的优先级降低。而sys_nice被放在了系统调用表中sys_call_table表中
6、进程信号量
常见另一篇笔记“Linux信号系统详解”
7、进程变量
exit_code表示进程退出时代码值,在进程退出时会根据调用exit传递的参数来设置exit_code的值
[进程的代码和数据相关]
start_code、end_code、end_data、brk、start_stack表示的是进程中的代码段、数据段、栈等信息在内存中的分布。其中start_code表示代码段和数据段的基地址(在Linux 0.11中,代码段和数据段共用一个段),在创建新进程的时候会设置进程的start_code的值,是一个线性地址。
end_code表示代码段的长度,进程的end_code的设置在当使用exec系统调用执行新的程序的时候,会设定当前进程的代码段、数据段和堆栈段的信息。在Linux 0.11中设定这个值的代码如下(在exec.c的do_execve中)
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
current->start_stack = p & 0xfffff000;
ex是一个struct exec类型的变量.struct exec的定义在a.out.h中,定义如下
struct
exec {
unsigned
long
a_magic;
/* Use macros N_MAGIC, etc for access */
unsigned
a_text;
/* length of text, in bytes */
unsigned
a_data;
/* length of data, in bytes */
unsigned
a_bss;
/* length of uninitialized data area for file, in bytes */
unsigned
a_syms;
/* length of symbol table data in file, in bytes */
unsigned
a_entry;
/* start address */
unsigned
a_trsize;
/* length of relocation info for text, in bytes */
unsigned
a_drsize;
/* length of relocation info for data, in bytes */
};
因此将上述的代码分解之后也就是
end_code=a_text; 表示代码段的长度
end_data=a_data+a_text; 表示数据段的长度,只不过在Linux 0.11中,代码和数据段合并为一个段,统称为数据段了。
所以这里的end_data的值包括了end_code的部分
brk=a_bss+a_data+a_text; 进程堆的结尾字段(动态内存分配部分),brk表示进程动态堆之前的所有长度,也就是可以将brk理解成堆的开始位置
start_stack=p&0xffff000设置进程的栈的起始地址。其中p的定义为
unsigned
long
p=PAGE_SIZE*MAX_ARG_PAGES-4; PAGE_SIZE表示每一个内存页的大小,MAX_ARG_PAGES为新程序分配给参数和环境变量使用的内存最大页数。那么p就表示的是分配给调用进程的参数和环境变量的最大内存值,这0.11中,p的值为128KB。这样之后,就让start_stack指向了参数的下一页(-4保证了start_stack不会指向参数的最后一页,而是指向下一页)。
对于bss部分,进程在执行新的程序的时候,会预先将一页内存数据设为0,操作的代码如下
i = ex.a_text+ex.a_data;
while
(i&0xfff)
put_fs_byte(0,(
char
*) (i++));
这里个人认为可以把i理解成一个逻辑地址。执行完成之后,进程的逻辑地址空间示意图如下(具体与文件相关的细节后续另外讨论)