本文讲解系统的进程管理相关内容,系统的进程管理是有关系统的所有进程的调度、排序、分配资源、创建、销毁等,是比较重要的内容。
CPU 内部有一个 RTC,会在上电的时候调用 mktime 函数算出从 1970 年的 1 月 1 日 0 时开始到当前开机点所过的秒数,给 mktime 函数传来的时间结构体的赋值是由初始化时从 RTC(CMOS)读出的参数,转换为时间存入全局变量中,并且会为 JIFFIES 所用
JIFFIES 是一个系统的时钟滴答,一个系统滴答是 10ms,定时器
调用 do_timer 函数
if (cpl) // CPL 变量是内核中用来指示被中断程序的特权 0:内核进程 3:被中断的是用户进程
current->utime++; // utime:用户程序的运行时间
else
current->stime++; // stime:内核程序的运行时间
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P299~P300 链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf 提取码:ygz8
进程是如何创建的?
内核态:不可抢占
用户态:可抢占
在内核初始化的过程中,会手动创建 0 号进程,0 号进程是所有进程的父进程
进程的初始化
for(;;) pause();
进程创建
fork
进程的创建就是对 0 号进程或者当前进程的复制
.align 2
_sys_fork:
call _find_empty_process
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp
1: ret
struct task_struct *p;
p = (struct task_struct *) get_free_page();
task[nr] = p;
p->pid = last_pid;
p->father = current->pid;
p->counter = p->priority;
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies;
p->tss.back_link = 0;
p->tss.esp0 = PAGE_SIZE + (long) p;
p->tss.ss0 = 0x10;
p->tss.eip = eip;
p->tss.eflags = eflags;
p->tss.eax = 0;
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff;
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
p->tss.ldt = _LDT(nr);
p->tss.trace_bitmap = 0x80000000;
如果当前进程使用了协处理器,那就设置当前创建进程的协处理器
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
进行老进程向新进程代码段、数据段(LDT)的拷贝
int copy_mem(int nr,struct task_struct * p)
如果父进程打开了某个文件,那么子进程也同样打开这个文件,所以将文件的打开计数+1
for (i=0; i<NR_OPEN;i++)
if (f=p->filp[i])
f->f_count++;
继承父进程相关属性
if (current->pwd)
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
设置进程两个段,并且结合刚才拷贝过来的,组装成一个进程
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
给程序的状态标志为可运行状态
p->state = TASK_RUNNING; /* do this last, just in case */
返回新创建进程的 pid
return last_pid;
具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P242~P252 和 P325~P333 链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf 提取码:ygz8
进程被创建到了链表中,如何再进行进一步的调佣和调度?请看下面进程调度!!!
void schedule(void)
-------> 进程调度函数
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && //信号不为空并且去除掉不能引发进程就绪状态的阻塞信号
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
} // 如何该进程为可中断睡眠状态,则如果该进程有非屏蔽信号出现就将进程的状态设置为就绪状态
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break; // 如果 c 不为空,则当前进程链表中,还有一些进程的时间片没有用完。
// 如果 c 为空,则所有进程的时间片都已经用完了
// 进行时间片的重分配
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + // 时间片的分配:counter = counter/2 + priority
(*p)->priority;
}
switch_to()
------> 进程切换函数
void sleep_on(struct task_struct **p)
if (current == &(init_task.task))
panic("task[0] trying to sleep");
void show_task(int nr,struct task_struct * p)
------> 打印 p->pid,p->state 打印栈的空闲大小具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P281~P302 链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf 提取码:ygz8
linux内核代码中以 syscall_、do_xxx 开头的基本上都是中断调用的函数
exit 是销毁函数 ------> 一个系统调用 ------> do_exit
void release(struct task_struct * p)
static inline int send_sig(long sig,struct task_struct * p,int priv)
static void kill_session(void)
int sys_kill(int pid,int sig)
kill- 不是杀死的意思,向对应的进程号或者进程组号发送任何信号
pid
pid > 0,给对应的 pid 发送 sig
else if (pid>0) while (--p > &FIRST_TASK) {
if (*p && (*p)->pid == pid)
if (err=send_sig(sig,*p,0))
retval = err;
}
pid = 0,给当前进程的进程组发送 sig
if (!pid) while (--p > &FIRST_TASK) {
if (*p && (*p)->pgrp == current->pid)
if (err=send_sig(sig,*p,1))
retval = err;
}
pid = -1,给任何进程发送
else if (pid == -1) while (--p > &FIRST_TASK)
if (err = send_sig(sig,*p,0))
retval = err;
pid < -1,给进程组号为 -pid 的进程组发送信号
else while (--p > &FIRST_TASK)
if (*p && (*p)->pgrp == -pid)
if (err = send_sig(sig,*p,0))
retval = err;
static void tell_father(int pid)
int do_exit(long code)
current->pid 就是当前关闭的进程
首先该函数会释放进程的代码段和数据段占用的内存
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
关闭进程打开的所有文件,对当前的目录和 i 节点进行同步(文件操作)
for (i=0 ; i<NR_OPEN ; i++)
if (current->filp[i])
sys_close(i);
iput(current->pwd);
current->pwd=NULL;
iput(current->root);
current->root=NULL;
iput(current->executable);
current->executable=NULL;
如果当前要销毁的进程有子进程,那么就让 1 号进程作为新的父进程(init 进程)
for (i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->father == current->pid) {
task[i]->father = 1;
if (task[i]->state == TASK_ZOMBIE)
/* assumption task[1] is always init */
(void) send_sig(SIGCHLD, task[1], 1);
}
如果使用了终端或者协处理器,那么将终端关闭 协处理器关闭
if (current->leader && current->tty >= 0)
tty_table[current->tty].pgrp = 0;
if (last_task_used_math == current)
last_task_used_math = NULL;
如果当前进程是一个会话头进程,则会终止会话中的所有进程
if (current->leader)
kill_session();
改变当前进程的运行状态,变成TASK ZOMBIE僵死状态,并且向其父进程发送SIGCHLD信号
current->state = TASK_ZOMBIE;
tell_father(current->father);
重新调度进程
schedule();
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
父进程在运行子进程的时候一般都会运行 wait waitpid 这两个函数(父进程等待某个子进程终止),当父进程收到 SIGCHLD 信号时父进程会终止僵死状态的子进程
首先父进程会把子进程的运行时间累加到自己的进程变量中
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽
release(*p);
具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P319~P325 链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf 提取码:ygz8