Linux进程的调度与退出

3、进程调度

	进程被创建到结构体链表中,但是被如何调度的?
	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中很多操作都是对于结构体的操作。

4、进程的退出

	在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;
}

你可能感兴趣的:(Linux,linux,运维,服务器)