您的位置:首页 > 运维架构 > Linux

Linux进程管理 进程状态和时钟滴答[哈工大操作系统实验]

2013-12-14 22:03 417 查看
进程管理和创建记录,是哈工大操作系统实验的内容。
涉及的技术内容包括“进程状态转换点定位”和“内核状态下写文件”。

本文主要讲解linux 0.11 系统[进程的调度转换知识],并给出哈工大实验自带的[内核文件读写代码]。

先讲简单的,内核文件读写:

一,搞定文件读写。

内核状态下的文件读写主要靠filp_open() ; filp_clos() ; vfs_read() ; vfs_write(),但是linux0.11的时候,这些东西还没出生呢,内核下只有个printk(String format , vars ...)来打印到屏幕,用法和printf()一样;

还好,工大给出了在内核态打印到文件的代码。这是一个添加到kernel/printk.c的外加系统调用,其实就是一个printk()函数的加强版本,你只需要用之前学过的添加系统调用的手法,在printk.c作如下的改动:

/*
*  linux/kernel/printk.c
*
*  (C) 1991  Linus Torvalds
*/

/*
* When in kernel-mode, we cannot use printf, as fs is liable to
* point to 'interesting' things. Make a printf with fs-saving, and
* all is well.
*/
#include <stdarg.h>
#include <stddef.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <sys/stat.h>
static char buf[1024];

extern int vsprintf(char * buf, const char * fmt, va_list args);

int printk(const char *fmt, ...)
{
va_list args;
int i;

va_start(args, fmt);
i=vsprintf(buf,fmt,args);
va_end(args);
__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $buf\n\t"
"pushl $0\n\t"
"call tty_write\n\t"
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (i):"ax","cx","dx");
return i;
}
static char logbuf[1024];
int fprintk(int fd, const char *fmt, ...)
{
va_list args;
int count;
struct file * file;
struct m_inode * inode;

va_start(args, fmt);
count=vsprintf(logbuf, fmt, args);
va_end(args);

if (fd < 3)	/* 如果输出到stdout或stderr,直接调用sys_write即可 */
{
__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $logbuf\n\t" /* 注意对于Windows环境来说,是_logbuf,下同 */
"pushl %1\n\t"
"call sys_write\n\t" /* 注意对于Windows环境来说,是_sys_write,下同 */
"addl $8,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (count),"r" (fd):"ax","cx","dx");
}
else	/* 假定>=3的描述符都与文件关联。事实上,还存在很多其它情况,这里并没有考虑。*/
{
if (!(file=task[0]->filp[fd]))	/* 从进程0的文件描述符表中得到文件句柄 */
return 0;
inode=file->f_inode;

__asm__("push %%fs\n\t"
"push %%ds\n\t"
"pop %%fs\n\t"
"pushl %0\n\t"
"pushl $logbuf\n\t"
"pushl %1\n\t"
"pushl %2\n\t"
"call file_write\n\t"
"addl $12,%%esp\n\t"
"popl %0\n\t"
"pop %%fs"
::"r" (count),"r" (file),"r" (inode):"ax","cx","dx");
}
return count;
}

然后相应修改unistd.h , system_call.s sys.h 就搞定了这个函数,向文件中写东西的语句如下:

fprintk(3,"%d",a);
二,理清思路

我们要做的事情总的来说就是两步:

1,找到进程小朋友们在哪里转换状态。

2,在转换状态的地方记录这次变化到文件。(直接调用fprintk就能自动生成process.log并按格式写入);

问题2其实已经解决了,就剩下1了,这里有一些知识需要了解:

一个进程的状态有如下几种宏:

TASK_RUNNING(准备好随时运行或正在运行)

TASK_INTERRUPTIBLE(进入睡眠,可以打断其 “进入睡眠” 直接改状态)

TASK_UNINTERRUPTIBLE(进入不可中断睡眠,完全进入睡眠后方可正式唤醒)

TASK_ZOMBIE(僵死状态,进程同学一路走好)

不同时间进入这三种状态,会产生一些教授们定义出来的状态:New /Ready /Running /Wait /Exit

三,开始找状态点:

1,fork.c 包含状态:new,ready

/*
*  linux/kernel/fork.c
*
*  (C) 1991  Linus Torvalds
*/

/*
*  'fork.c' contains the help-routines for the 'fork' system call
* (see also system_call.s), and some misc functions ('verify_area').
* Fork is rather simple, once you get the hang of it, but the memory
* management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'
*/
#include <errno.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#ifndef timep
#define timep jiffies
#endif
extern void write_verify(unsigned long address);

long last_pid=0;

void verify_area(void * addr,int size)
{
unsigned long start;

start = (unsigned long) addr;
size += start & 0xfff;
start &= 0xfffff000;
start += get_base(current->ldt[2]);
while (size>0) {
size -= 4096;
write_verify(start);
start += 4096;
}
}

int copy_mem(int nr,struct task_struct * p)
{
unsigned long old_data_base,new_data_base,data_limit;
unsigned long old_code_base,new_code_base,code_limit;

code_limit=get_limit(0x0f);
data_limit=get_limit(0x17);
old_code_base = get_base(current->ldt[1]);
old_data_base = get_base(current->ldt[2]);
if (old_data_base != old_code_base)
panic("We don't support separate I&D");
if (data_limit < code_limit)
panic("Bad data_limit");
new_data_base = new_code_base = nr * 0x4000000;
p->start_code = new_code_base;
set_base(p->ldt[1],new_code_base);
set_base(p->ldt[2],new_data_base);
if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
printk("free_page_tables: from copy_mem\n");
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}
return 0;
}

/*
*  Ok, this is the main fork-routine. It copies the system process
* information (task[nr]) and sets up the necessary registers. It
* also copies the data segment in it's entirety.
*/
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
long fs,long es,long ds,
long eip,long cs,long eflags,long esp,long ss)
{
struct task_struct *p;
int i;
struct file *f;

p = (struct task_struct *) get_free_page();
if (!p)
return -EAGAIN;
task[nr] = p;
*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
p->state = TASK_UNINTERRUPTIBLE;
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));
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
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 */
//这里有修改
fprintk(3,"%ld\tN\t%ld\n",last_pid,timep);//进程新建好了	,这里的timep宏就是 jiffies
fprintk(3,"%ld\tJ\t%ld\n",last_pid,timep);//进程肯定准备好了
//printk("%d J %d\n",last_pid,jiffies);
return last_pid;
}

int find_empty_process(void)
{
int i;

repeat:
if ((++last_pid)<0) last_pid=1;
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat;
for(i=1 ; i<NR_TASKS ; i++)
if (!task[i])
return i;
return -EAGAIN;
}


2.sched.c 包含状态:ready, sleep ,run

/*
*  linux/kernel/sched.c
*
*  (C) 1991  Linus Torvalds
*/

/*
* 'sched.c' is the main kernel file. It contains scheduling primitives
* (sleep_on, wakeup, schedule etc) as well as a number of simple system
* call functions (type getpid(), which just extracts a field from
* current-task
*/
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#include <signal.h>

#define _S(nr) (1<<((nr)-1))
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

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<j && !((char *)(p+1))[i])
i++;
printk("%d (of %d) chars free in kernel stack\n\r",i,j);
}

void show_stat(void)
{
int i;

for (i=0;i<NR_TASKS;i++)
if (task[i])
show_task(i,task[i]);
}

#define LATCH (1193180/HZ)

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

union task_union {
struct task_struct task;
char stack[PAGE_SIZE];
};

static union task_union init_task = {INIT_TASK,};

long volatile jiffies=0;
long startup_time=0;
struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
long * a;
short b;
} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
*  'math_state_restore()' saves the current math information in the
* old math state array, and gets the new ones from the current task
*/
void math_state_restore()
{
if (last_task_used_math == current)
return;
__asm__("fwait");
if (last_task_used_math) {
__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
}
last_task_used_math=current;
if (current->used_math) {
__asm__("frstor %0"::"m" (current->tss.i387));
} else {
__asm__("fninit"::);
current->used_math=1;
}
}

/*
*  '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) {
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;
//there is a ready
fprintk(3,"%ld\tJ\t%ld\n",(*p)->pid,jiffies);//这里转换,进入就绪
}
}

/* this is the scheduler proper: */

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;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
if(current != p)
{if(current->state == 0)//这里转换:如果下一个要 RUN 的不是正在 RUN 的,给你补上一条ready,因为你将停下来进入就绪状态,换下一个跑。current指针指向当前正在运行的进程。
fprintk(3,"%ld\tJ\t%ld\n",current->pid,jiffies);
//下面转换:如果下一个要 RUN 的就是当前正在 RUN 的,那就不用打印了,否则打印RUN。
//there is a running
fprintk(3,"%ld\tR\t%ld\n",p->pid,jiffies);
}
switch_to(next);//切到 p 去运行
}

int sys_pause(void)
{
if(current->state!=TASK_INTERRUPTIBLE){
current->state = TASK_INTERRUPTIBLE;
//there is one
if(current->pid!=0) //这里有一个,可中断的睡眠,就是sleep(wait),守护进程0总在等待,就不用显示它了,闹心。
fprintk(3,"%ld\tW\t%ld\n",current->pid,jiffies);
}
schedule();
return 0;
}

void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;

if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
//there is one
if(current->pid!=0) //这里有一个,不可中断的睡眠,就是sleep(wait),守护进程0总在等待,就不用显示它了,闹心。
fprintk(3,"%ld\tW\t%ld\n",current->pid,jiffies);
schedule();
*p=tmp;
if (tmp ){//&& tmp->state!=0){
tmp->state=TASK_RUNNING;
//there is one
//if(tmp->pid!=current->pid)//这里,temp被唤醒就绪,判断防止重复打印
fprintk(3,"%ld\tJ\t%ld\n",tmp->pid,jiffies);
}
}

void interruptible_sleep_on(struct task_struct **p)
{
struct task_struct *tmp;

if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp=*p;
*p=current;
repeat:	current->state = TASK_INTERRUPTIBLE;
//there is one wait
if(current->pid!=0)//这里和上面同理
fprintk(3,"%ld\tW\t%ld\n",current->pid,jiffies);
schedule();
if (*p && *p != current) {

//这里,从调度返回,意味着p被唤醒
fprintk(3,"%ld\tJ\t%ld\n",(*p)->pid,jiffies);
(**p).state=0;//这里就唤醒了p
goto repeat;
}
*p=tmp;
if (tmp){
//if(tmp->state!=0){
tmp->state=0;
//	if(tmp->pid!=current->pid)//此处与上同理
fprintk(3,"%ld\tJ\t%ld\n",tmp->pid,jiffies);
//	}
}
}

void wake_up(struct task_struct **p)
{
if (p && *p) {
if((*p)->state!=TASK_RUNNING){
fprintk(3,"%ld\tJ\t%ld\n",(*p)->pid,jiffies);//这里,同理。
(**p).state=TASK_RUNNING;
}
}
}

//下边是软盘驱动什么的,不改。

3,exit.c
包含状态:exit,

/*
*  linux/kernel/exit.c
*
*  (C) 1991  Linus Torvalds
*/

#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>
#include <asm/segment.h>
#define VARCHEN 0
#define timep jiffies
int sys_pause(void);
int sys_close(int fd);

void release(struct task_struct * p)
{
int i;

if (!p)
return;
for (i=1 ; i<NR_TASKS ; i++)
if (task[i]==p) {
task[i]=NULL;
free_page((long)p);
schedule();
return;
}
panic("trying to release non-existent task");
}

static inline int send_sig(long sig,struct task_struct * p,int priv)
{
if (!p || sig<1 || sig>32)
return -EINVAL;
if (priv || (current->euid==p->euid) || suser())
p->signal |= (1<<(sig-1));
else
return -EPERM;
return 0;
}

static void kill_session(void)
{
struct task_struct **p = NR_TASKS + task;

while (--p > &FIRST_TASK) {
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!
*/
int sys_kill(int pid,int sig)
{
struct task_struct **p = NR_TASKS + task;
int err, retval = 0;

if (!pid) while (--p > &FIRST_TASK) {
if (*p && (*p)->pgrp == current->pid)
if ((err=send_sig(sig,*p,1)))
retval = err;
} else if (pid>0) while (--p > &FIRST_TASK) {
if (*p && (*p)->pid == pid)
if ((err=send_sig(sig,*p,0)))
retval = err;
} else if (pid == -1) while (--p > &FIRST_TASK) {
if ((err = send_sig(sig,*p,0)))
retval = err;
} else while (--p > &FIRST_TASK)
if (*p && (*p)->pgrp == -pid)
if ((err = send_sig(sig,*p,0)))
retval = err;
return retval;
}

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

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));
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);    //发出子进程退出的信号
//printk("\nPID:%d Zombie %d\n",task[i]->pid,jiffies);
fprintk(3,"%ld\tE\t%ld\n",task[i]->pid,timep);    //恭喜这位进程获得将死称号
}
}
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;
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();
current->state = TASK_ZOMBIE;   //这里是状态转换,但是因为EXIT这种状态转换很分散,“赋值为将死”可能出现在任何涉及关闭操作的系统调用中,不好找。然而对zombie的检查却都在上方。所以exit的打印就都截在了循环判断中。
//如解除下面注释,就会让一些程序“死两遍”...
//fprintk(3,"%d E %d\n",current->pid,jiffies);
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:    fprintk(3,"%ld\tE\t%ld\n",(*p)->pid,timep);     //这里有一个状态转换,state=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;
if(current->state!=TASK_INTERRUPTIBLE){
current->state=TASK_INTERRUPTIBLE;    //这里变成了等待状态
if(current->pid!=VARCHEN)              //还是防止0进程捣乱。
fprintk(3,"%ld\tW\t%ld\n",current->pid,timep);
}
schedule();
if (!(current->signal &= ~(1<<(SIGCHLD-1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}


这样,状态的寻找就结束了,目标是尽快完成实验的同学可以不用往下看了。

下面简单说说进程调度时间片的事情。

1,什么是时间片?

我们知道,机器的最小时间单位叫做“时钟周期”,也就是晶振11.88MHZ,是执行一条原子指令的时间。几个时钟周期凑合在一起,就是“机器周期”,用于完成一小套动作。

jiffie滴答类似时钟周期,时间片类似机器周期。

时间片标志着CPU对进程的关注程度,高权限的进程得到的时间片大,包含很多滴答,低权限的就小,滴答数相应就少。

2,修改了又怎样?

进程得到的平均时间片大,则进程间切换就不频繁,当时间片无限制时,就是FIFO先来先服务,你完事儿了下一个。反之就来回折腾。

3,怎么修改啊?

[1]让每个滴答的时间变大,这样时间片的绝对时间就变长了。可以在sched.h头文件修改滴答的定义:#define .... 200 . 200HZ的滴答就是每个滴答5ms。

你把它调成100,就是10ms了。

[2]让时间片包含的滴答数目普遍增多,在schedule()里面有一个级数优先级算法 count = count/2 + priority,你给count再加一个常数,就让每个count变大了。

Aurora 极光城
講技術,說人話。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: