2012.2.3 - Linux内核 - 进程运行轨迹的跟踪与统计

Posted on 2013-02-13 23:49  SnakeHunt2012  阅读(1148)  评论(0)    收藏  举报

本文始作于2012年2月3日,刊登于人人网,于2013年2月13日迁移至此

这个实验是进程调度,上网查,进程是操作系统最重要的概念,看过实验内容以后不太明白,但是明显感觉这个实验不像前两个那么简单了,而且看样子也很重要。好吧,先看文献:

《剖析》5.7 - 进程控制;
百度fork()和wait()系统调用;
《剖析》7.1 - init/main.c;
《剖析》8.10 - kernel/fork.c;
《剖析》8.7 - kernel/sched.c。

看完实验要求,再结合《剖析》,我觉得这个实验的切口应该在init/main.c那里,因为一切过程都是从这里开始的。

我先看的5.8,不过看着看着就看不下去了,说的太深,涉及到的后面的东西太多,还是先从main.c入手,然后不懂什么在回来找也好。第七章的序言说,如果能完全理解这里调用的所有程序,那么看完这章内容后,你应该对Linux内核有大至的了解。可见这一章是入门的关键,要好好看了。还有就是导学那段说,在阅读这些初始化子程序时,最好是跟着被调用的程序深入进去看,如果是在看不下去了,就暂时放一放,继续看下一个调用,在有些理解之后,再继续研究没有看完的地方。看来这个实验会非常耗时间,工程非常大啊,不过还是得加紧步伐,明天就不整别的了,赶紧看,一号就可以用iphone上的流量了,五号就开始看和声学了。话说今天也够点背的,本打算用电脑架起无线热点来给iphone分wifi来着,可是发现开了wifi之后正常的pppoe就断开了,一连就断,百度也没百着,还是在google上搜到了,说network-manager跟pppoeconf使不能同时使用的,不建议命令行跟界面混着使,要用network-manager就都用network-manager,不过我想回机房应该就没有这个问题了,因为机房是静态ip,我通常用的就是network-manager。




tty是什么?终端设备
Linux提供了execl、execlp、execle、execv、execvp和execve等六个用以执行一个可执行文件的函数(统称为exec函数)。



接下来要挨个看内核始化的函数了:

mem_init(memory_start, memory_end);
从而就涉及到了内存的页式管理,物理地址,逻辑地址,线性地址,虚拟内存,这些内存管理有关的概念。原来所谓页,就是内存中的4KB,我这样理解肯定是非常不准确,但是现在看到的地方只能用出来这些。

trap init();
这就涉及到了陷阱门(Trapdoor)。百度:“陷阱门通常是指编程员在设计系统时有意建立的进入手段。当程序运行时,在正确的时间按下正确的键,或提供正确的参数,你就能绕过程序提供的正常安全检查和错误跟踪检查。”,还得知道中断描述符表。看这trap.c就得看asm.s,全都是AT&T汇编,看来最近真得看看GNU汇编了。读来读去还是没读懂。

blk_dev_init();
从而就得了解什么是块设备,字符设备。但其实也没多大关系。

其实这些初始化函数跟这次实验真没啥大关系,我跳过去了,现在开始看fork();
在指导手册上说:“需要对kernel下的fork.c、sched.c有通盘的了解,而exit.c也会涉及到”。

悲催了,看了好四五天了,感觉根本都没有头绪,刚才有仔细看了看实验指导书,发现这个实验的脉络是:

首先学会多进程编程,学会用fork(),编写出一个可以运行在Linux 0.11上的程序,这个程序的模板已经写好了,但是要完成它必须得会fork(),这就是让我们练习使用fork()等系统调用。悲催的是sunner第4课的视频就是讲fork()的,但是这个视频没有声音,所以就只能自己学了。

然后就是在Linux 0.11上修改内核,创建一个process.log文件,并且使内核对进程的所有动作都记载在proces.log文件上。然后运行测试程序process.c,把系统对这些进程的动作记录下来。

最后研究这个记录process.log,然后修改时间片,再尝试。

那现在开始第一部分:多进程编程。刚开始看“多进程编程”的介绍,就感觉这个部分的容量相当于实验一的容量了,这次实验过让够大的啊,怪不得都说操作系统的实验难呢,前面两个实验看不出来,这回见识了,确实难,都不讲啊,都自己找,没头绪,还容易走偏,浪费时间,这要是在学校,肯定一大半人得吐血,而且还是在课程那么紧张的学期,好恐怖。那么这里就记录一下我学习多进程编程的足迹,也就是看过的网站和资料:
Linux下的多进程编程初步:http://blog.chinaunix.net/space.php?uid=8038672&do=blog&id=80975

这篇文章中关键的一段是这个:

fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就"分叉"了,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:

 1 #include <stdio.h>
 2 
 3 main()
 4 {
 5     int i;
 6     if ( fork() == 0 )
 7         /* 子进程程序 */
 8         for ( i = 1; i <1000; i ++ ) printf("This is child process\n");
 9     else {
10         /* 父进程程序*/
11         for ( i = 1; i <1000; i ++ ) printf("This is process process\n");
12     } 
13 }

 

   程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。
   那么调用这个fork函数时发生了什么呢?fork函数启动一个新的进程,前面我们说过,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现,这将是我们下面的内容。既然它们如此相象,系统如何来区分它们呢?这是由函数的返回值来决定的。对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。在操作系统中,我们用ps函数就可以看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数fork()下面的代码,而我们就是利用fork()函数对父子进程的不同返回值用if...else...语句来实现让父子进程完成不同的功能,正如我们上面举的例子一样。我们看到,上面例子执行时两条信息是交互无规则的打印出来的,这是父子进程独立执行的结果,虽然我们的代码似乎和串行的代码没有什么区别。 

这让我一下子明白了fork()是怎样实现的了,之前一直不理解为什么fork()要将子程序的所有段都设成一样的,原来他的作用机理是这样的。子程序是由父程序生出来的那我们把子程序的代码写在哪里呢?答案就是子程序的代码跟父程序是写在一起的,用if...else...的方式分开,这算是设计上的一个技巧吧,还真不好看出来...,UNIX设计得好聪明,那怎么实现分别执行呢?是这样的,我们参照上面的程序,当程序执行到fork()函数的时候,系统里面就会调用系统调用fork()函数,在系统调用里面发生了这些事情。首先,将父进程的所有都复制一份到另一个地方,这一份就是子进程,然后给CPU发送指令让他同时运行这两个进程(其实是迅速切换着运行这两个进程,这是"并行"的秘密),由于子进程刚开始一切都跟父进程一样,所以他的代码指针也停留在父进程此刻停留的代码位置,所以,这两个进程此刻都停留在fork()函数那个地方,这条语句都刚刚执行一半,至于停留在哪,再往细我就不敢描述了,具体还要看fork()的系统代码,但反正都是在等待fork()的返回值,接下来就是这队父子进程分叉的地方了(fork的中文解释是"分叉"),在fork()的剩余部分里,要完成返回值的返回,返回值的时候,系统fork()函数对父进程的返回值是它分出来的子进程的进程号(pid),这是个非零值,然而对子进程,fork()给出的返回值是0!这样,父进程接下来的代码就会跳到else语句处执行,这里面写的也就是父进程应该执行的代码,而子进程就会在if(fork() == 0){}的里面继续执行代码,这里也就是写子程序该做的事的地方。关键过程就是这样,但是在fork()的时候是有很多很复杂的调整工作的,具体代码我也没深研究。这个地方真是UNIX的伟大之处,fork.c这几行代码打开了多进程的先河,创造了第一个多进程模式,KenThompson、DennisRitchie你俩简直太聪明了,估计现在的多进程都是这样实现的,真可谓前无古人后无来者,我都忍不住想再讲一遍了。

Linux下的多进程编程:http://linux.chinaunix.net/doc/program/2001-08-21/643.shtml

接下来要开始分析样本程序process.c了。现在一看马上就知道要干什么了,目的是要在main(){}里面写若干个多进程进程,就是写几个if...else...,而每个进程都是有事情要做的,具体做什么呢,sunner已经替我们写好了,就是里面已经实现好的cpuio_bound(int last, int cpu_time, int io_time);函数,这个函数可以模拟各种不同的cpu调用和io调用,就是模拟真实程序有可能出现的调用情况,你可以通过他的参数来设置它cpu和io调用的情况,总运行时间,io运行时间,cpu运行时间都可以设置,很方便,这样就不用亲自找哪个程序来写进去了,不然的话还得自己找那种既需要调用cpu又需要调用io的程序。我写的是这个:

int main(int argc, char * argv[])
{
    /* process one */
    if (fork() == 0)
        cpuio_bound(5, 1, 0);
    else
        cpuio_bound(5, 1, 1);
    /* process two */
    if (fork() == 0) {
        if (fork() == 0)
            cpuio_bound(3, 0, 1);
        else
            cpuio_bound(2, 1, 1);
    }
    else {
        if (fork() == 0)
            cpuio_bound(2, 0, 1);
        else
            cpuio_bound(3, 1, 1);
    }
    return 0;
}

 

按照实验提示,运行起来用top看一下:


果然有好几个a.out在运行。

再用ps aux | grep a.out看一下:

同时运行着好几个进程。

那么第一个任务算完成了吧。好几天没有进展了,今天总算有了点,主要是前两天看实验的思路不对,应该紧盯着指导书,老在《剖析》上看,结果看看就出不来了,第一个实验也是,所以实践证明,还是指导书更重要,还得一个字不落的看。现在已经是早上3:56了。