陈民禾——原创做品转载请注明出处—— 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000函数
一.复习上周内容学习
上周主要学习了Linux中的系统调用的过程,如图所示就是系统调用的大体过程:优化
一.关于进程调度的一些基本概念spa
fork():进程是处于执行期的程序以及相关资源的总称,进程在建立它的时候开始存活,在Linux系统中。这一般是调用fork()系统的结果,该系统调用经过复制一个现有进程来建立一个全新的进程,调用fork()的进程成为父进程,新产生的进程称之为子进程,在调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行,fork()系统从内核返回两次:一次返回到父进程,另外一次回到新的子进程。其中fork()其实是由clone()系统调用实现的。线程
exec():建立新的进城以后会当即执行新的进程,接着调用exec()这组函数就能够建立新的地址空间,并把新的地址空间载入其中。3d
进程描述符:内核把进程的列表存放在叫作任务队列的双向循环列表中,链表中的类型都是task_struct、成为进程描述符的结构,进程描述符包含一个具体进程的全部信息,能完整的描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态,还有其它的更多信息。指针
thread_info:每一个进程的task_struct存放在内核栈的尾端,调试
进程状态转换:code
- TASK_RUNNING具体是就绪仍是执行,要看系统当前的资源分配状况;blog
- TASK_ZOMBIE也叫僵尸进程
三.进程建立
3.1 写时拷贝
只有在须要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资
源的复制只有在须要写入的时候才进行,在此以前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。在页根本不会被写人的状况下(举例来讲,fork()后当即调用exec(})它们就无须复制了。fork()的实际开销就是复制父进程的页表以反给予进程建立惟一的进程描述符。在通常状况下,进程建立后都会立刻运行一个可执行的文件,这种优化能够避免拷贝大量根本就不会被使用的数据〈地址空间里经常包含数十她的数据〉。因为Unix 强调进程快速执行的能力,因此这个优化是很重要的。
3. 2 fork()
Linux 经过clone()系统调用实现fork() 。这个调用经过一系列的参数标志来指明父、子进程须要共享的资源〈关于这些标志更多的信息请参考本章后面3.4 节〉。fork()、vfork()和一clone()库函数都根据各自须要的参数杨L志去调用clone(),而后由clone()去调用do_fork().do_fork 完成了建立中的大部分工做,它的定义在kemeVfork.c 文件中。该函数调用copy_process()函数,而后让进程开始运行。copy_process()函数完成的工做颇有意思:
l )调用dup_task_ struct()为新进程建立一个内核枝、也read_info 结构和task_struct,这些值与当前进程的值相同。此时, 子进程和父进程的描述符是彻底相同的。
2 )检查并确保新建立这个子进程后,当前用户所拥有的进程数目没有超出绘色分配的资源
的限制.
3 )子进程着手使本身与父进程区别开来。进程描述符内的许多成员都要被清0 或设为初始值.那些不是继承而来的进程描述符成员,主要是统计信息。task_struct 中的大多数数据都依然未被修改.
4 ) 子进程的状态被设置为TASK_UNJNTERRUPTIBLE,以保证它不会投入运行。
5 ) copy _process()调用copy_flags()以更新task_struct 的组ags 成员.代表进程是否拥有超级用户权限的PF_SUPE盯RIV 标志被清0。代表进程尚未调用exec()函数的PF_FOR.KNOEXEC标志被设置。
6 )调用alloc _pid()为新进程分配一个有效的PID。
7 )根据传递给clone()的参数标志, copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在通常状况下,这些资源会被给定进程的全部线程共享:不然,这些资源对每一个进程是不一样的,所以被拷贝到这里。的最后, copy_process()傲扫尾工做并返回一个指向子进程的指针。再回到do_fork()函数,若是copy_process()函数成功返回,新建立的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行。.由于通常子进程都会立刻调用exec()函数,这样能够避免写时拷贝的额外开销,若是父进程首先执行的话,有可能会开始向地址空间写入。
四.实验过程
1.更新menu内核,而后删除test_fork.c以及test.c(以减小对以后实验的影响)
2.编译内核,能够看到fork命令
3.启动gdb调试,并对主要的函数设置断点
4.在MenuOS中执行fork,就会发现fork函数停在了父进程中
5.继续执行以后,停在了do_fork的位置。而后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,能够看到dst = src(也就是复制父进程的struct)
6.在copy_thread中,能够看到把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化
7.到了15九、160行的代码就是把压入的代码再放到子进程中:
*children = *current_pt_regs(); childregs->ax = 0;
8.164行,是肯定返回地址
p->thread.ip = (unsigned long) ret_from_fork;
实验感想:
只是我第一次这么早就完成博客,在此次实验的过程当中我了解了进程间调度的基本方法,还本身实践了gdb对内核代码的调试,颇有意义。可执行程序代码( Unix 称其为代码段, text section)。一般进程还要包含其余资源,像打开的文件,挂起的信号,内核内部数据,处理器状态, 一个或多个具备内存映射的内存地址空间及一个或多个执行线程( thread of execution ),固然还包括用来存放全局变量的数据段等。实际上,进程就是正在执行的程序代码的实时结果。