程序只可以通过stat_log.py的简单检查。
实验报告大体写了一下。
//===================================================================================
单进程编程简单,比较容易实现,CPU利用率低。
多进程编程复杂,需要复杂且灵活的调度算法,充分利用CPU资源.
修改INIT_TASK中的priority就可以改变时间片大小。
变化不大的原因是,子进程连续占用cpu的时间要比时间片大很多。
//====================================================================================
init/main.c
kernel/fork.c
kernel/sched.c
kernel/exit.c
kernel/printk.c
这几个文件
init/main.c 需要在把log文件关联到文件描述符3 ,0 1 2分别是stdin stdout 和 stderr ,按照如下方式修改就好
……
move_to_user_mode();
/***************添加开始***************/
setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0); //建立文件描述符0和/dev/tty0的关联
(void) dup(0); //文件描述符1也和/dev/tty0关联
(void) dup(0); //文件描述符2也和/dev/tty0关联
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);
/***************添加结束***************/
if (!fork()) { /* we count on this going ok */
init();
}
……
dup是一个系统调用,在unistd.h之中有定义。支持1个参数和2个参数的形式
int dup(int fd);
int dup2(int fd1,int fd2);
两个均为复制一个现存的文件的描述
两个函数的返回:若成功为新的文件描述,若出错为-1;
由dup返回的新文件描述符一定是当前可用文件描述中的最小数值。用dup2则可以用fd2参数指定新的描述符数值。如果fd2已经打开,则先关闭。若fd1=fd2,则dup2返回f d2,而不关闭它。通常使用这两个系统调用来重定向一个打开的文件描述符。
当然,大家还要知道DUP是1,2-苯二甲酸二正十一酯,不溶于水,溶于脂。
这样,就会在启动内核的时候打开文件了。
有一点疑问是,log文件是在进程0之中建立的,那么它会记录到进程0的创建嘛?
//======================================================================================================================================
之后修改kernel/fork.c kernel/sched.c kernel/exit.c 三个文件,找到进程状态转换的点,并把它输出到log文件之中。
其中fork这个东西很神奇,
声明是这样
pid_t fork(void);
pid_t 是一个宏定义,其实就是个int,定义在 #include<sys/type.h>中。
fork这个函数的神奇之处在于,它会返回两次,一次在父进程中,一次在子进程中,子进程返回0,父进程返回进程ID,出错返回-1.
原因是子进程复制时复制了父进程的堆寨段,所以两个进程都会停留在fork函数中,等待返回。
详细内容可见:http://baike.baidu.com/view/1952900.htm
一定要仔细看一看,看不懂就不会写progress.c.........................................
附上一个内核进程状态表
内核表示 | 含义 |
TASK_RUNNING | 可运行 |
TASK_INTERRUPTIBLE | 可中断的等待状态 |
TASK_UNINTERRUPTIBLE | 不可中断的等待状态 |
TASK_ZOMBIE | 僵死 |
TASK_STOPPED | 暂停 |
TASK_SWAPPING | 换入/换出 |
fork.c的修改如下
#include <errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <asm/system.h>
extern void write_verify(unsigned long address);
long last_pid=0;
void verify_area(void * addr,int size)
{
...
}
int copy_mem(int nr,struct task_struct * p)
{
...
}
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)
{
p->start_time = jiffies;
/*在上面一行初始化了进程的开始时间所以赶快输出一条进程创建的Log*/
fprintk(3,"%ld\t%c\t%ld\n",last_pid,'N',jiffies);
...
p->state = TASK_RUNNING; /* do this last, just in case */
/*在上面一行改变了进程的状态这里输出一个进入就绪队列的Log*/
/*进程中Running表示的是可以运行,并不是正在运行*/
fprintk(3,"%ld\t%c\t%ld\n",last_pid,'J',jiffies);
return last_pid;
}
int find_empty_process(void)
{
...
}
exit.c的修改如下
#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>
int sys_pause(void);
int sys_close(int fd);
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)
{
...
}
static void tell_father(int pid)
{
...
}
int do_exit(long code)
{
...
}
int sys_exit(int error_code)
{
...
}
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;
/*输出一条进程退出的Log*/
/*TASK_STOPED状态只是将当前进程转入睡眠状态,收到SIG_CONT信号时会被唤醒*/
/*TASK_ZOMBIE状态则是当前进程被KILL,并发送信号给父进程*/
fprintk(3,"%ld\t%c\t%ld\n",flag,'E',jiffies);
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;
/*输出一条等待的Log*/
/*这里要注意一下输出wait的时候要判断一下 pid 是不是等于0 如果等于0 就不输出Log*/
/*0号进程是守护进程,cpu空闲的时候一直在waiting,输出它的话是不会通过脚本检查的哦*/
if (current->pid!=0)
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
schedule();
if (!(current->signal &= ~(1<<(SIGCHLD-1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}
sched.c 的修改如下
#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)
{
...
}
void show_stat(void)
{
...
}
#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 };
void math_state_restore()
{
...
}
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;
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",(*p)->pid,'J',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->state == TASK_RUNNING && current != task[next])
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'J',jiffies);
if(current != task[next])
/*输出可运行的Log*/
fprintk(3,"%ld\t%c\t%ld\n",task[next]->pid,'R',jiffies);
switch_to(next);
}
int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE;
/*检查并输出等待的Log*/
if (current->pid != 0)
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',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;
/*检查并输出等待的Log*/
if (current->pid != 0)
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
schedule();
*p = tmp;
if (tmp)
{
tmp->state=TASK_RUNNING;
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",tmp->pid,'J',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;
/*检查并输出等待的Log*/
if (current->pid != 0)
fprintk(3,"%ld\t%c\t%ld\n",current->pid,'W',jiffies);
schedule();
if (*p && *p != current) {
(**p).state=TASK_RUNNING;
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",(**p).pid,'J',jiffies);
goto repeat;
}
*p=tmp;
if (tmp)
{
tmp->state=TASK_RUNNING;
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",tmp->pid,'J',jiffies);
}
}
void wake_up(struct task_struct **p)
{
if (p && *p) {
if((**p).state != TASK_RUNNING){
/*输出就绪的Log*/
fprintk(3,"%ld\t%c\t%ld\n",(**p).pid,'J',jiffies);
(**p).state=TASK_RUNNING;
}
}
}
...
====================================================================================================================================
最后写一个自己的proress.c。
perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量error 的值来决定要输出的字符串。不加也行。
然后大家可能对,这个主函数不太理解,这和fork的原理有关,很神奇。
具体看上文介绍。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>
#define HZ 100
/*
* 此函数按照参数占用CPU和I/O时间
* last: 函数实际占用CPU和I/O的总时间,不含在就绪队列中的时间,>=0是必须的
* cpu_time: 一次连续占用CPU的时间,>=0是必须的
* io_time: 一次I/O消耗的时间,>=0是必须的
* 如果last > cpu_time + io_time,则往复多次占用CPU和I/O
* 所有时间的单位为秒
*/
void cpuio_bound(int last, int cpu_time, int io_time)
{
struct tms start_time, current_time;
clock_t utime, stime;
int sleep_time;
while (last > 0)
{
/* CPU Burst */
times(&start_time);
/* 其实只有t.tms_utime才是真正的CPU时间。但我们是在模拟一个
* 只在用户状态运行的CPU大户,就像“for(;;);”。所以把t.tms_stime
* 加上很合理。*/
do
{
times(¤t_time);
utime = current_time.tms_utime - start_time.tms_utime;
stime = current_time.tms_stime - start_time.tms_stime;
} while ( ( (utime + stime) / HZ ) < cpu_time );
last -= cpu_time;
if (last <= 0 )
break;
/* IO Burst */
/* 用sleep(1)模拟1秒钟的I/O操作 */
sleep_time=0;
while (sleep_time < io_time)
{
sleep(1);
sleep_time++;
}
last -= sleep_time;
}
}
void main()
{
pid_t c_p1;
pid_t c_p2;
pid_t c_p3;
pid_t c_p4;
if((c_p1 = fork())==0 )
{
printf( "in child1 \n");cpuio_bound( 5 , 2 , 2);
}
else if((c_p2 = fork())==0)
{
printf( "in child2 \n");cpuio_bound( 5 , 4 , 0);
}
else if((c_p3 = fork())==0)
{
printf( "in child3 \n");cpuio_bound( 5,0 , 4);
}
else if((c_p4 = fork())==0)
{
printf( "in child4 \n");cpuio_bound( 4 , 2 , 2);
}
else if(c_p1==-1||c_p2==-1||c_p3==-1||c_p4==-1)
{
perror("fork");
exit(1);
}
else
{
printf("====================This is parent process====================\n");
printf("My pid is %d\n",getpid());
printf("The pid of child1 is %d\n",c_p1);
printf("The pid of child2 is %d\n",c_p2);
printf("The pid of child3 is %d\n",c_p3);
printf("The pid of child4 is %d\n",c_p4);
}
wait(NULL);
}