进程被创建到结构体链表中,但是被如何调度的?
void schedule(void)进程调度函数 在.c文件中定义
void switch_to(next) 进程切换函数 在.h文件中定义 使用汇编语言编写的程序
辅助函数:打印内核中的pid号以及state以及栈堆内容
//nr就是pid
//这个函数就是用来打印pid号和state
void show_task(int nr,struct task_struct * p)
{
int i,j = 4096-sizeof(struct task_struct);
printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state);
i=0;
while (i
进程状态:运行状态(在task_struct中用state来表示)
可中断睡眠状态
不可中断睡眠状态
暂停状态
僵死状态
// 宏定义运行状态
#define TASK_RUNNING 0 //可以被运行 只有在这个状态才能进行进程切换
#define TASK_INTERRUPTIBLE 1 //可以被信号中断 变成running(waitpid函数在子进程发出SIGCHLD状态时 父进程才会结束子进程)
#define TASK_UNINTERRUPTIBLE 2 //只能被wakeup唤醒 变成running 多个进程同时使用一块资源,进程被占用时 会进入sleep(不可中断)
#define TASK_ZOMBIE 3 //收到SIGSTOP SIGTSTP SIGTTIN这几个信号就会暂停
#define TASK_STOPPED 4 //进程停止运行 但是父进程还未将其清空(僵死进程父进程没有回收该进程)
遍历task_struct链表 比较counter(时间片的大小)谁大谁先执行
/*
* 'schedule()' is the scheduler function. This is GOOD CODE! There
* probably won't be any reason to change this, as it should work well
* in all circumstances (ie gives IO-bound processes good response etc).
* The one thing you might take a look at is the signal-handler code here.
*
* NOTE!! Task 0 is the 'idle' task, which gets called when no other
* tasks can run. It can not be killed, and it cannot sleep. The 'state'
* information in task[0] is never used.
*/
// 时间片分配
void schedule(void)
{
int i,next,c;
struct task_struct ** p;//结构体指针的指针用来指向这个结构体数组
/* check alarm, wake up any interruptible tasks that have got a signal */
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)//从最后往前遍历
if (*p) {//alarm是用来设置警告,比如jiffies有1000个可能其中一些需要警告那么就用alarm来实现
if ((*p)->alarm && (*p)->alarm < jiffies) { //如果当前的alarm小于系统脉冲 系统超前
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
//~(_BLOCKABLE & (*p)->blocked
//&&(*p)->state==TASK_INTERRUPTIBLE
//用来排除非阻塞信号
//信号不为空 排除阻塞信号 可以被信号所中断的task_struct
//如果该进程为可中断睡眠状态 则如果该进程有非屏蔽信号出现就将该进程的状态设置为running
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}
/* this is the scheduler proper: */
// 以下思路,循环task列表 根据counter大小决定进程切换 找时间片最大的进程
while (1) {
c = -1;
next = 0;
i = NR_TASKS; //64
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;//进程为空就继续循环
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)//找出c最大的task
c = (*p)->counter, next = i; //c中保存counter的最大值 next中保存编号
}
if (c) break;//如果c不是0(是最大的时间片),就终结循环,说明找到了
//如果为0的话 所有进程的时间片都已经用完了 下面函数 进行时间片的重新分配
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)//进程不为空
// 这里很关键,在低版本内核中,是进行优先级时间片轮转分配,这里搞清楚了优先级和时间片的关系
//counter = counter/2 + priority //priority优先级 运行态的时间片为0 +priority 非运行态的时间片除2+priority
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
//切换到下一个进程 这个功能使用宏定义完成的
//将需要切换的进程赋值给当前进程指针
//进行进程的上下文切换 上下文(程序运行的cpu的特殊寄存器 通用寄存器 TSS等信息 + 堆栈的信息)
switch_to(next); //转到汇编宏定义中完成的
}
// 当某个进程想访问CPU资源,但是CPU资源被占用 访问不到,就会调用sleep_on函数 进行进程休眠
//电源管理模式 或者某个进程不使用时进行自动的休眠。
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)//如果传进来的是空的 就返回
return;
if (current == &(init_task.task))//当前进程是0号 init_task.task是0号进程
panic("task[0] trying to sleep");//就打印并且返回 因为0号进程不能被睡眠
tmp = *p;
*p = current;//这两步相当于 给休眠链表添加了一个新node 添加头节点
// 其实核心就是把state置为TASK_UNINTERRUPTIBLE
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp) //tmp变量不会被释放 在进程的轮转过程中 会创建tmp 形成一个链表
tmp->state=0;
}
void wake_up(struct task_struct **p)
{
if (p && *p) {
(**p).state=0; //将不可运行睡眠状态 变成可运行状态对应0
*p=NULL;
}
}
小总结 :在linux系统中进程就是系统调度 通过一个task_struct结构体形成的链表进行操作,进程的调度简单来讲就是时间片的分配以及对于最大值算法的应用。在不同版本的系统中调度算法有所不同。同时在linux中很多操作都是对于结构体的操作。
在0.11版本的linux中只有64个进程
在系统中以syscall_XXX do_XXX开头的都是系统调用
//进程销毁
//1. 释放进程的代码段和数据段占用的内存
//2. 关闭进程打开的所有文件,对当前目录和i节点进行同步(文件操作)
//3. 如果当前要销毁的进程有子进程,就让1号进程作为新的父进程 孤儿进程的父亲是init进程 0号进程又称为idle进程 是唯一一个不是通过fork创建的(在0号进程中创建的1号进程)1号进程又称为init进程 是所有进程的父进程
//4. 如果当前进程是一个会话头进程,则会终止会话中的所有进程
//5. 改变当前进程的运行状态,变成TASK_ZOMBIE(僵尸进程)状态,并且向其父进程发送SIGCHLD信号,说明自己要死了 可以通过信号操作来回收子进程
//1. 父进程在运行子进程时一般都会运行wait waitpid这两个函数,用来父进程等待子进程终止
//2. 当父进程收到SIGCHLD信号时,父进程会终止僵死状态的子进程
//3. 父进程会把子进程的运行时间累加到自己的运行时间上 utime stime cstime cutime
//4. 把对应子进程的进程描述结构体进行释放,置空数组空槽
//exit.c函数,对于进程的操作都会使用到进程调度函数,进程进程的重新调度。封装了release函数 waitpid send_sig函数 kill等
//释放对应内存页(代码段 数据段 堆栈)
void release(struct task_struct * p)
{
int i;
if (!p)
return;
for (i=1 ; i<NR_TASKS ; i++)//在task[]中进行遍历
if (task[i]==p) {
task[i]=NULL;
free_page((long)p);//释放内存页
schedule();//重新进行进程调度
return;
}
panic("trying to release non-existent task");
}
//给指定的p进程发送信号
static inline int send_sig(long sig,struct task_struct * p,int priv)
{
if (!p || sig<1 || sig>32) //信号在2-31
return -EINVAL;
if (priv || (current->euid==p->euid) || suser()) //当前用户 或者超级用户
p->signal |= (1<<(sig-1)); //才能发信号
else
return -EPERM;
return 0;
}
//关闭session 终止会话 并发送SIGHUP信号
static void kill_session(void)
{
struct task_struct **p = NR_TASKS + task;
while (--p > &FIRST_TASK) {//从最后一个开始扫描(不包括0进程)
if (*p && (*p)->session == current->session)
(*p)->signal |= 1<<(SIGHUP-1);
}
}
/*
* XXX need to check permissions needed to send signals to process
* groups, etc. etc. kill() permissions semantics are tricky!
*/
// 系统调用 向任何进程 发送任何信号(类比shell中的kill命令也是发送信号的意思)
int sys_kill(int pid,int sig)
{
struct task_struct **p = NR_TASKS + task;//将指针移动到节点的最后指向最后
int err, retval = 0;
if (!pid) while (--p > &FIRST_TASK) { //pid等于0的
if (*p && (*p)->pgrp == current->pid) //如果输入的进程组号等于当前进程号
if (err=send_sig(sig,*p,1)) //发送信号
retval = err;
} else if (pid>0) while (--p > &FIRST_TASK) {//pid>0给对应进程发送信号
if (*p && (*p)->pid == pid) //找到指定的pid号
if (err=send_sig(sig,*p,0)) //发送信号
retval = err;
} else if (pid == -1) while (--p > &FIRST_TASK)//pid=-1给任何进程发送
if (err = send_sig(sig,*p,0)) //不找 直接发信号
retval = err;
else while (--p > &FIRST_TASK)//pid<-1 给进程组发送信息
if (*p && (*p)->pgrp == -pid) //给进程组等于负pid号的进程组发信号
if (err = send_sig(sig,*p,0))
retval = err;
return retval;
}
//告诉父进程要死了 子进程发送SIGCHILD信号
static void tell_father(int pid)
{
int i;
if (pid)
for (i=0;i<NR_TASKS;i++) {
if (!task[i])
continue;
if (task[i]->pid != pid)
continue;
task[i]->signal |= (1<<(SIGCHLD-1));//找到父亲发送SIGCHLD信号
return;
}
/* if we don't find any fathers, we just release ourselves */
/* This is not really OK. Must change it to make father 1 */
printk("BAD BAD - no father found\n\r");
release(current);//释放子进程
}
//命名规则
//以do开头 以syscall开头基本都是终端调用函数
int do_exit(long code)
{
int i;
//释放内存页
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); //代码段数据段
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
//current->pid就是当前需要关闭的进程
for (i=0 ; i<NR_TASKS ; i++) //遍历进程结构体
if (task[i] && task[i]->father == current->pid) {//如果当前进程是某个进程的父进程
task[i]->father = 1;//就让1号进程作为新的父进程 让init进程接管当前进程
if (task[i]->state == TASK_ZOMBIE)//如果是僵死状态
/* assumption task[1] is always init */
(void) send_sig(SIGCHLD, task[1], 1);//给父进程发送SIGCHLD
}
for (i=0 ; i<NR_OPEN ; i++)//每个进程能打开的最大文件数NR_OPEN=20 文件描述符 每个进程都有自己的文件管理
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;
if (current->leader && current->tty >= 0) //当前进程是头号进程且是tty控制台 串口 鼠标 显示器
tty_table[current->tty].pgrp = 0;//清空终端
if (last_task_used_math == current)
last_task_used_math = NULL;//清空协处理器
if (current->leader)
kill_session();//清空session
current->state = TASK_ZOMBIE;//设为僵死状态
current->exit_code = code;
tell_father(current->father); //告诉父进程重新调度进程
schedule();
return (-1); /* just to suppress warnings */
}
// 定义系统调用
int sys_exit(int error_code)
{
return do_exit((error_code&0xff)<<8);
}
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
int flag, code;
struct task_struct ** p;
verify_area(stat_addr,4);//验证区域是否可以用
repeat:
flag=0;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) { //从后向前遍历
if (!*p || *p == current)
continue;
if ((*p)->father != current->pid)
continue;
if (pid>0) {
if ((*p)->pid != pid)
continue;
} else if (!pid) {
if ((*p)->pgrp != current->pgrp)
continue;
} else if (pid != -1) {
if ((*p)->pgrp != -pid)
continue;
}
switch ((*p)->state) {
case TASK_STOPPED:
if (!(options & WUNTRACED))
continue;
put_fs_long(0x7f,stat_addr);
return (*p)->pid;
case TASK_ZOMBIE: //父进程把子进程的运行实践累加到自己的运行实践上
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
flag = (*p)->pid;
code = (*p)->exit_code;
release(*p); //释放
put_fs_long(code,stat_addr);
return flag;
default:
flag=1;
continue;
}
}
if (flag) {
if (options & WNOHANG)
return 0;
current->state=TASK_INTERRUPTIBLE;
schedule();
if (!(current->signal &= ~(1<<(SIGCHLD-1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}