进程是伟大的抽象概念,有了抽象才有多道程序。在linux中进程是一个struct,其中0号进程的属性是硬编码的,之后的进程则都是0号进程的fork出来的。子程序都是继承父进程的属性,只需要改变其中某些属性,ldt描述符和tss是最关键的,ldt规定了进程使用的逻辑地址,tss规定了进程的对cpu的一些设定。
struct task_struct
{
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
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;
};
- state 是进程当前的状态,调度程序会根据状态来选择调度逻辑
- counter 代表进程剩余可以运行的滴答数
- priority 进程的优先级
- signal 是进程的信号位图,注册过的信号会在位图中置位
- sigaction 是一个信号处理的自定义回调数组
- blocked 进程当前不想处理的信号阻塞位图
- exit_code 进程退出码,父进程需要
- start_code 进程的线性开始地址
- end_code 结束地址
- end_data 数据结束地址
- brk 进程的代码和数据总长度,刚开始fork时候,brk=end_data,后期用户程序malloc会增加brk
- start_stack 进程的堆栈开始地址
- pid 进程号
- father 父进程pid
- pgrp 进程所属的进程组号
- session 会话号
- leader 会话老大号
- uid 当前进程用户id
- euid 有效用户id
- suid 用户标识号
- gid 组号
- egit 有效组号
- sgid 用户组标识号
- alarm 报警滴答数
- utime 进程用户态运行时间
- stime 进程内核态运行时间
- start_time 开始运行时间
- used_math 是否使用数学协处理器
- tty 当前进程使用的tty
- umask 创建新文件使用的属性屏蔽位
- pwd 当前进程的工作路径
- root 进程的根节点
- executable 进程的执行文件节点指针
- close_on_exec 进程文件描述符位图,每个位代表一个文件描述符,规定在exec后要关闭的fd
- flip 当前进程打开的fd数组
- ldt 进程的局部描述表结构
- tss 进程的TSS信息结构
进程是怎么切换的
cpu是不知道进程的存在的,但我们知道一个瞬间时,cpu的一些核心寄存器的值代表了该进程的所有运行时信息。如果我们把这时候的寄存器的值保存起来(保存到TSS中),然后改变cs,ds和eip的值,就可以让cpu去执行另外位置的指令,这就是完成了切换
0号进程的创建
// 调度程序的初始化子程序。
void sched_init (void)
{
int i;
struct desc_struct *p; // 描述符表结构指针。
if (sizeof (struct sigaction) != 16) // sigaction 是存放有关信号状态的结构。
panic ("Struct sigaction MUST be 16 bytes");
// 设置初始任务(任务0)的任务状态段描述符和局部数据表描述符(include/asm/system.h,65)。
set_tss_desc (gdt + FIRST_TSS_ENTRY, &(init_task.task.tss));
set_ldt_desc (gdt + FIRST_LDT_ENTRY, &(init_task.task.ldt));
// 清任务数组和描述符表项(注意i=1 开始,所以初始任务的描述符还在)。
p = gdt + 2 + FIRST_TSS_ENTRY;
for (i = 1; i < NR_TASKS; i++)
{
task[i] = NULL;
p->a = p->b = 0;
p++;
p->a = p->b = 0;
p++;
}
/* 清除标志寄存器中的位NT,这样以后就不会有麻烦 */
// NT 标志用于控制程序的递归调用(Nested Task)。当NT 置位时,那么当前中断任务执行
// iret 指令时就会引起任务切换。NT 指出TSS 中的back_link 字段是否有效。
// __asm__ ("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); // 复位NT 标志。
_asm pushfd; _asm and dword ptr ss:[esp],0xffffbfff; _asm popfd;
ltr (0); // 将任务0 的TSS 加载到任务寄存器tr。
lldt (0); // 将局部描述符表加载到局部描述符表寄存器。
// 注意!!是将GDT 中相应LDT 描述符的选择符加载到ldtr。只明确加载这一次,以后新任务
// LDT 的加载,是CPU 根据TSS 中的LDT 项自动加载。
// 下面代码用于初始化8253 定时器。
outb_p (0x36, 0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p (LATCH & 0xff, 0x40); /* LSB */// 定时值低字节。
outb (LATCH >> 8, 0x40); /* MSB */// 定时值高字节。
// 设置时钟中断处理程序句柄(设置时钟中断门)。
set_intr_gate (0x20, &timer_interrupt);
// 修改中断控制器屏蔽码,允许时钟中断。
outb (inb_p (0x21) & ~0x01, 0x21);
// 设置系统调用中断门。
set_system_gate (0x80, &system_call);
}