1.首先创造日志文件,需要在linux-0.11/init/main.c中添加如下代码
/*
*main中打开process.log文件
*/
setup((void *) &drive_info);//加载文件系统
(void) open("/dev/tty0",O_RDWR,0);//打开/dev/tty0,建立文件描述符0和/dev/tty0的关联
(void) dup(0);//让文件描述符1也和/dev/tty0关联
(void) dup(0);//让文件描述符2也和/dev/tty0关联
//加载日志文件并将其编号变为3
(void) open("/var/process.log",O_CREAT|O_TRUNC|O_WRONLY,0666);
/*open dup返回的一定是未使用的最小的描述符数值 参见《UNIX环境高级编程》(第三版) P51*/
/*添加结束*/
这段代码建立了文件描述符0、1和2,它们分别就是stdin、stdout和stderr。这三者的值是系统标准(Windows也是如此),不可改变。可以把log文件的描述符关联到3。文件系统初始化,描述符0、1和2关联之后,才能打开log文件,开始记录进程的运行轨迹。0666并不是 文件描述符,只是权限标记。修改后如图
2.修改kernel/printk.c文件,添加打印功能
在文件中添加以下代码
头文件:
#include <linux/sched.h>
#include <sys/stat.h>
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;
}
3.跟踪进程的状态
首先要知道在什么情况下进程的状态会发生修改,分别是进程创建的时候,进程调度的时候,进程销毁的时候,因此我们需要修改三个对应的文件去记录进程的修改。
修改/linux-0.11/kernel/forke.c
进程创建函数的是:copy_process,在此函数中添加如下代码
/*
*新建一个进程
*/
fprintk(3,"%d\tN\t%d\n",p->pid,jiffies);
/*
*新建 => 就绪
*/
fprintk(3,"%d\tJ\t%d\n",p->pid,jiffies);
修改后如下图
同理我们接下来修改/linux-0.11/kernel/sched.c
从以上修改中我们已经知道进程p->state改变的时候就需要打印信息
修改schedule函数添加以下代码(调度时)
/*可中断睡眠 => 就绪*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
/*编号为next的进程 运行*/
if(current->pid != task[next] ->pid)
{
/*时间片到时程序 => 就绪*/
if(current->state == TASK_RUNNING)
fprintk(3,"%d\tJ\t%d\n",current->pid,jiffies);
fprintk(3,"%d\tR\t%d\n",task[next]->pid,jiffies);
}
系统暂停,进程又运行变中断在sys_pause中添加
/*
*当前进程 运行 => 可中断睡眠
*/
if(current->pid != 0)
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
进程睡眠时,在sleep_on中添加
/*
*当前进程进程 => 不可中断睡眠
*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/*
*原等待队列 第一个进程 => 唤醒(就绪)
*/
fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
在 interruptible_sleep_on中添加如下代码
/*
*这一部分属于 唤醒队列中间进程,通过goto实现唤醒 队列头进程 过程中Wait
*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
/*
*当前进程进程 => 可中断睡眠 同上
*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
/*
*原等待队列 第一个进程 => 唤醒(就绪)
*/
fprintk(3,"%d\tJ\t%d\n",tmp->pid,jiffies);
唤醒进程,进入就绪状态,修改wake_up函数,添加以下代码
/*
*唤醒 最后进入等待序列的 进程
*/
fprintk(3,"%d\tJ\t%d\n",(*p)->pid,jiffies);
最终修改截图如下(每个函数中的修改大致相似,都是追踪状态的变化)
进程销毁的时候,修改exit.c
在do_exit函数中添加如下代码
/*
*退出一个进程
*/
fprintk(3,"%d\tE\t%d\n",current->pid,jiffies);
在sys_waitpid函数中添加如下代码
/*
*当前进程 => 等待
*/
fprintk(3,"%d\tW\t%d\n",current->pid,jiffies);
以上两个代码段都是在在状态变更之后插入
以上修改完成后我们可以在ubuntu下Linux文件夹下编译,先make clean再make
4.编译成功后我们需要编写一个程序process.c用来跟踪子进程(注意这个文件要放到Linux内核中,因此我们需要先挂载,然后把文件导入/hdc/usr中,文件代码如下(原博客中此代码有错)
#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(¤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;
}
}
挂载复制文件进入内核
sudo ./mount-hdc
cp process.c ./hdc/usr/root/
5.打开bochs,编译文件,运行
gcc -o process process.c
./process
可看到如下界面
程序运行结束后我们通过sync命令将缓存区命令加载到硬盘
然后我们在/var文件夹下就可以看到process.log文件
6.为了更直观的看出一个进程各种状态的时间.,我们用Python脚本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 process.log 0 1 2 3 4 5
可以看到如下:
注意:不知道什么原因,在process.log文件中可能存在很多重复行,运行脚本时会报错,此时只需要删除旧文件,再次运行process程序生成新的文件即可。
7.在linux-0.11/include/linux/sched.h的INIT_TASK中修改时间片的大小
修改后如下(0,15,15->0,15,100)
#define INIT_TASK \
/* state etc */ { 0,15,100, \
/* signals */ 0,{{},},0, \
/* ec,brk... */ 0,0,0,0,0,0, \
/* pid etc.. */ 0,-1,0,0,0, \
/* uid etc */ 0,0,0,0,0,0, \
/* alarm */ 0,0,0,0,0,0, \
/* math */ 0, \
/* fs info */ -1,0022,NULL,NULL,NULL,0, \
/* filp */ {NULL,}, \
{ \
{0,0}, \
/* ldt */ {0x9f,0xc0fa00}, \
{0x9f,0xc0f200}, \
}, \
/*tss*/ {0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\
0,0,0,0,0,0,0,0, \
0,0,0x17,0x17,0x17,0x17,0x17,0x17, \
_LDT(0),0x80000000, \
{} \
}, \
}
再次编译内核,并运行process程序,观察区别。
运行脚本结果如下
至此,程序结束(最后记得修改回原时间片大小)
出现的问题:无法对bochs下的文件进行修改