操作系统实验(3:进程运行轨迹的跟踪与统计)

实验目的

1.过对进程运行轨迹的跟踪来让实验者对进程的概念形象化。

2.对进程运行轨迹跟踪的基础上进行相应的数据统计,如统计进程的等待时间(在等待队列中时间),从而能对进程调度算法进行实际的量化评价(如算出平均等待时间),更进一步加深对进程调度概念和调度算法的理解,获得能在实际操作系统上对调度算法进行实验数据对比的直接经验。

实验内容

1.在Linux 0.11上实现上述进程运行轨迹的跟踪,基本任务是在Linux 0.11内核中维护一个日志文件/etc/process.log,要求把从操作系统启动后到操作系统关机的所有的进程的 的运行轨迹(生命轨迹)都记录在这一log文件中,记录的内容可参照上面给出的实例。

2.在进程运行轨迹的跟踪基础上,统计所有进程的等待时间、完成时间、运行时间等信息。然后针对现有的Linux 0.11调度算法,计算该算法导致的平均等待时间,平均完成时间,平均吞吐量等数据。

3.实验者需要修改现有的Linux 0.11调度算法,然后再统计这些时间,并和现有的Linux 0.11调度算法对比,体会调度算法的实验验证。

实验步骤

实验步骤参考了简书上寒夏凉秋的文章。链接如下:
https://www.jianshu.com/p/8746879ab9bb
(侵删)

1.修改init/main.c文件

我们需要创建日志文件来记录进程的状态转变,要保证这个日志文件能在多个程序中对其进行写入。打开linux-0.11/init下的main.c,修改如下:
在这里插入图片描述

2.修改kernel/printk.c文件,添加打印功能

在这里插入图片描述

修改后的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/sched.h>
#include <sys/stat.h>

#include <linux/kernel.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;
}

//modify======
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 %%fs\n\t"
	"pushl %0\n\t"
	"pushl $logbuf\n\t"
	"pushl %1\n\t"
	"call sys_write\n\t"
	"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;
}

3.跟踪进程状态

(1)修改fork.c
在这里插入图片描述
在这里插入图片描述
(2)修改sched.c
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(3)修改exit.c
在这里插入图片描述
在这里插入图片描述

4.创建process.c,用例创建多个进程进行跟踪

process.c的代码如下:

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/times.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>

#define HZ    100

void cpuio_bound(int last, int cpu_time, int io_time);

int main(int argc, char * argv[])
{
       pid_t Pid1;
       pid_t Pid2;
       pid_t Pid3;

       Pid1 = fork(); 
       if (Pid1 < 0) printf("error in fork!"); 
       else if (Pid1 == 0) 
            {
                 printf("child process 1:\n");
                 cpuio_bound(5, 2, 2);
            }

       Pid2 = fork();
       if (Pid2 < 0) printf("error in fork!"); 
       else if (Pid2 == 0) 
            {
                 printf("child process 2:\n");
                 cpuio_bound(5, 4, 0);
            }

       Pid3 = fork();
       if (Pid3 < 0) printf("error in fork!"); 
       else if (Pid3 == 0) 
            {
                 printf("child process 3:\n");
                 cpuio_bound(5, 0, 4);
            }

       printf("This process's Pid is %d\n", getpid());
       printf("Pid of child process 1 is %d\n", Pid1);
       printf("Pid of child process 2 is %d\n", Pid2);
       printf("Pid of child process 3 is %d\n", Pid3);
       wait(NULL);
       wait(NULL);
       wait(NULL);
       return 0;
}
/*
 * 此函数按照参数占用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(&start_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;
    }
}

挂载hdc,将process.c复制到usr/root/下,运行linux-0.11,编译process并运行。
在这里插入图片描述
得到的日志文件如下:
在这里插入图片描述
在bochs中输入sync将缓冲区信息加载到硬盘。

创建stat_log.py脚本来查看各种进程状态的时间,脚本代码如下:

#!/usr/bin/python
import sys
import copy

P_NULL = 0
P_NEW = 1
P_READY = 2
P_RUNNING = 4
P_WAITING = 8
P_EXIT = 16

S_STATE = 0
S_TIME = 1

HZ = 100

graph_title = r"""
-----===< COOL GRAPHIC OF SCHEDULER >===-----
             [Symbol]   [Meaning]
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
             number   PID or tick
              "-"     New or Exit 
              "#"       Running
              "|"        Ready
              ":"       Waiting
                    / Running with 
              "+" -|     Ready 
                    \and/or Waiting
-----===< !!!!!!!!!!!!!!!!!!!!!!!!! >===-----
"""

usage = """
Usage: 
%s /path/to/process.log [PID1] [PID2] ... [-x PID1 [PID2] ... ] [-m] [-g]
Example:
# Include process 6, 7, 8 and 9 in statistics only. (Unit: tick)
%s /path/to/process.log 6 7 8 9
# Exclude process 0 and 1 from statistics. (Unit: tick)
%s /path/to/process.log -x 0 1
# Include process 6 and 7 only and print a COOL "graphic"! (Unit: millisecond)
%s /path/to/process.log 6 7 -m -g
# Include all processes and print a COOL "graphic"! (Unit: tick)
%s /path/to/process.log -g
"""

class MyError(Exception):
    pass

class DuplicateNew(MyError):
    def __init__(self, pid):
        args = "More than one 'N' for process %d." % pid
        MyError.__init__(self, args)

class UnknownState(MyError):
    def __init__(self, state):
        args = "Unknown state '%s' found." % state
        MyError.__init__(self, args)

class BadTime(MyError):
    def __init__(self, time):
        args = "The time '%d' is bad. It should >= previous line's time." % time
        MyError.__init__(self, args)

class TaskHasExited(MyError):
    def __init__(self, state):
        args = "The process has exited. Why it enter '%s' state again?" % state
        MyError.__init__(self, args)

class BadFormat(MyError):
    def __init__(self):
        args = "Bad log format"
        MyError.__init__(self, args)

class RepeatState(MyError):
    def __init__(self, pid):
        args = "Previous state of process %d is identical with this line." % (pid)
        MyError.__init__(self, args)

class SameLine(MyError):
    def __init__(self):
        args = "It is a clone of previous line."
        MyError.__init__(self, args)

class NoNew(MyError):
    def __init__(self, pid, state):
        args = "The first state of process %d is '%s'. Why not 'N'?" % (pid, state)
        MyError.__init__(self, args)

class statistics:
    def __init__(self, pool, include, exclude):
        if include:
            self.pool = process_pool()
            for process in pool:
                if process.getpid() in include:
                    self.pool.add(process)
        else:
            self.pool = copy.copy(pool)

        if exclude:
            for pid in exclude:
                if self.pool.get_process(pid):
                    self.pool.remove(pid)
    
    def list_pid(self):
        l = []
        for process in self.pool:
            l.append(process.getpid())
        return l

    def average_turnaround(self):
        if len(self.pool) == 0:
            return 0
        sum = 0
        for process in self.pool:
            sum += process.turnaround_time()
        return float(sum) / len(self.pool)

    def average_waiting(self):
        if len(self.pool) == 0:
            return 0
        sum = 0
        for process in self.pool:
            sum += process.waiting_time()
        return float(sum) / len(self.pool)
    
    def begin_time(self):
        begin = 0xEFFFFF
        for p in self.pool:
            if p.begin_time() < begin:
                begin = p.begin_time()
        return begin

    def end_time(self):
        end = 0
        for p in self.pool:
            if p.end_time() > end:
                end = p.end_time()
        return end

    def throughput(self):
        return len(self.pool) * HZ / float(self.end_time() - self.begin_time())

    def print_graphic(self):
        begin = self.begin_time()
        end = self.end_time()

        print graph_title

        for i in range(begin, end+1):
            line = "%5d " % i
            for p in self.pool:
                state = p.get_state(i)
                if state & P_NEW:
                    line += "-"
                elif state == P_READY or state == P_READY | P_WAITING:
                    line += "|"
                elif state == P_RUNNING:
                    line += "#"
                elif state == P_WAITING:
                    line += ":"
                elif state & P_EXIT:
                    line += "-"
                elif state == P_NULL:
                    line += " "
                elif state & P_RUNNING:
                    line += "+"
                else:
                    assert False
                if p.get_state(i-1) != state and state != P_NULL:
                    line += "%-3d" % p.getpid()
                else:
                    line += "   "
            print line

class process_pool:
    def __init__(self):
        self.list = []
    
    def get_process(self, pid):
        for process in self.list:
            if process.getpid() == pid:
                return process
        return None

    def remove(self, pid):
        for process in self.list:
            if process.getpid() == pid:
                self.list.remove(process)

    def new(self, pid, time):
        p = self.get_process(pid)
        if p:
            if pid != 0:
                raise DuplicateNew(pid)
            else:
                p.states=[(P_NEW, time)]
        else:
            p = process(pid, time)
            self.list.append(p)
        return p

    def add(self, p):
        self.list.append(p)

    def __len__(self):
        return len(self.list)
    
    def __iter__(self):
        return iter(self.list)

class process:
    def __init__(self, pid, time):
        self.pid = pid
        self.states = [(P_NEW, time)]
    
    def getpid(self):
        return self.pid

    def change_state(self, state, time):
        last_state, last_time = self.states[-1]
        if state == P_NEW:
            raise DuplicateNew(pid)
        if time < last_time:
            raise BadTime(time)
        if last_state == P_EXIT:
            raise TaskHasExited(state)
        if last_state == state and self.pid != 0: # task 0 can have duplicate state
            raise RepeatState(self.pid)

        self.states.append((state, time))

    def get_state(self, time):
        rval = P_NULL
        combo = P_NULL
        if self.begin_time() <= time <= self.end_time():
            for state, s_time in self.states:
                if s_time < time:
                    rval = state
                elif s_time == time:
                    combo |= state
                else:
                    break
            if combo:
                rval = combo
        return rval

    def turnaround_time(self):
        return self.states[-1][S_TIME] - self.states[0][S_TIME]

    def waiting_time(self):
        return self.state_last_time(P_READY)

    def cpu_time(self):
        return self.state_last_time(P_RUNNING)

    def io_time(self):
        return self.state_last_time(P_WAITING)

    def state_last_time(self, state):
        time = 0
        state_begin = 0
        for s,t in self.states:
            if s == state:
                state_begin = t
            elif state_begin != 0:
                assert state_begin <= t
                time += t - state_begin
                state_begin = 0
        return time


    def begin_time(self):
        return self.states[0][S_TIME]

    def end_time(self):
        return self.states[-1][S_TIME]
        
# Enter point
if len(sys.argv) < 2:
    print usage.replace("%s", sys.argv[0])
    sys.exit(0)

# parse arguments
include = []
exclude = []
unit_ms = False
graphic = False
ex_mark = False

try:
    for arg in sys.argv[2:]:
        if arg == '-m':
            unit_ms = True
            continue
        if arg == '-g':
            graphic = True
            continue
        if not ex_mark:
            if arg == '-x':
                ex_mark = True
            else:
                include.append(int(arg))
        else:
            exclude.append(int(arg))
except ValueError:
    print "Bad argument '%s'" % arg
    sys.exit(-1)

# parse log file and construct processes
processes = process_pool()

f = open(sys.argv[1], "r")

# Patch process 0's New & Run state
processes.new(0, 40).change_state(P_RUNNING, 40)

try:
    prev_time = 0
    prev_line = ""
    for lineno, line in enumerate(f):

        if line == prev_line:
            raise SameLine
        prev_line = line

        fields = line.split("\t")
        if len(fields) != 3:
            raise BadFormat

        pid = int(fields[0])
        s = fields[1].upper()

        time = int(fields[2])
        if time < prev_time:
            raise BadTime(time)
        prev_time = time

        p = processes.get_process(pid)

        state = P_NULL
        if s == 'N':
            processes.new(pid, time)
        elif s == 'J':
            state = P_READY
        elif s == 'R':
            state = P_RUNNING
        elif s == 'W':
            state = P_WAITING
        elif s == 'E':
            state = P_EXIT
        else:
            raise UnknownState(s)
        if state != P_NULL:
            if not p:
                raise NoNew(pid, s)
            p.change_state(state, time)
except MyError, err:
    print "Error at line %d: %s" % (lineno+1, err)
    sys.exit(0)

# Stats
stats = statistics(processes, include, exclude)
att = stats.average_turnaround()
awt = stats.average_waiting()
if unit_ms:
    unit = "ms"
    att *= 1000/HZ
    awt *= 1000/HZ
else:
    unit = "tick"
print "(Unit: %s)" % unit
print "Process   Turnaround   Waiting   CPU Burst   I/O Burst"
for pid in stats.list_pid():
    p = processes.get_process(pid)
    tt = p.turnaround_time()
    wt = p.waiting_time()
    cpu = p.cpu_time()
    io = p.io_time()

    if unit_ms:
        print "%7d   %10d   %7d   %9d   %9d" % (pid, tt*1000/HZ, wt*1000/HZ, cpu*1000/HZ, io*1000/HZ)
    else:
        print "%7d   %10d   %7d   %9d   %9d" % (pid, tt, wt, cpu, io)
print "Average:  %10.2f   %7.2f" % (att, awt)
print "Throughout: %.2f/s" % (stats.throughput())

if graphic:
    stats.print_graphic()

对该脚本加上权限:sudo chmod +x ./stat_log.py

分析结果:./stat_log.py ./hdc/var/process.log 4
在这里插入图片描述

5.修改调度算法的时间片

打开linux-0.11/include/linux/sched.h,将state etc的第三个值改为30,再次编译linux-0.11并运行bochs,使用stat_log.py查看如下:
在这里插入图片描述

发布了24 篇原创文章 · 获赞 9 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/realfancy/article/details/89598670