图片 2

基于版本4,进程管理

By admin in 茶社 on 2019年11月7日

《奔跑吧linux内核》3.1笔记,不足之处还望大家批评指正

定义

进程就是处于执行期的程序。实际上,进程就是正在执行代码的实际结果。
线程是在进程中活动的对象,每个线程都拥有独立的程序计数器,进程栈以及一组进程寄存器。内核的调度对象是线程,而不是
进程。

进程是Linux内核最基本的抽象之一,它是处于执行期的程序。它不仅局限于一段可执行代码(代码段),还包括进程需要的其他资源。在Linux内核中常被称作任务。

进程的两种虚拟机制

  1. 虚拟处理器:每个线程独有,不能共享
  2. 虚拟内存:同一个进程中的线程可以共享

线程被称为轻量级进程,是操作系统调度的最小单元,通常一个进程可以拥有多个线程。

进程描述符及任务结构

  • 任务队列:存放进程列表的双向循环链表
  • task_struct:进程描述符,包含一个具体进程的所有信息。2.6以后的版本通过slab动态生成task_struct。
  • thread_info:线程描述符,

进程和线程的区别在于进程拥有独立的资源空间,而线程则共享进程的资源空间。

PID

唯一的进程标志值。int类型,为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768,这个值最大可增加到400万。进程的PID存放在进程描述符中。

问题一:在内核中如何获取当前进程的task_struct数据结构?

进程状态

进程描述符中的state域记录进程当前的状态,进程一共有五中状态,分别为:

  • TASK_RUNNING 运行
  • TASK_INTERRUPTIBLE 可中断
  • TASK_UNINTERRUPTIBLE 不可中断
  • __TASK_TRACED 被其他进程跟踪的进程
  • __TASK_STOPPED 进程停止执行

  内核有一个常用的常量current用于获取当前进程task_struct数据结构,它利用了内核栈的特性。首先通过sp寄存器获取当前内核栈的地址,对齐后获取struct
thread_info数据结构指针,最后通过thread_info->task成员获取task_struct数据结构。图1为linux内核栈的结构图。

进程上下文

通常进程的代码在用户空间执行,当执行了系统调用或触发了某个异常时,它就陷入了内核空间。此时,我们称内核处于进程上下文中。

图片 1图1
内核栈

进程创建

  1. 写时拷贝,父子进程共享同一个地址空间,将页的拷贝推迟到实际发生写入时才进行。这个优化可以避免创建进程时拷贝大量不被使用的数据。
  2. 在进程中调用fork()会通过复制一个现有进程来创建一个新进程,调用fork()的进程是父进程,创建的进程是子进程。fork()函数从内核返回两次,一次是返回父进程,另一次返回子进程。Linux通过
    clone(SIGCHLD, 0);系统调用实现fork()。
  3. vfork()
    不拷贝父进程的页表项,其它与fork功能相同。系统实现:clone(CLONE_VFORK
    | CLONE_VM | SIGCHLD, 0);
  4. exec()这组函数可以创建新的地址空间,并把新的程序载入其中。

 

线程实现

在Linux内核中线程看起来就是一个普通的进程,只是和其它一些进程共享某些资源,如地址空间。

  1. 创建线程同样使用clone实现,只是需要传递一些参数标志来指明需要共享的资源:clone(CLONE_VM
    | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
  2. 内核线程没有独立的地址空间,只在内核空间运行,不切换到用户空间上去,只能由内核线程创建。

问题二:下面程序会打印几个“_”?

进程终结

当一个进程终结时必须释放它所占有的资源。进程主动终结发生在进程调用exit()系统调用时,当然它还有可能被动终结。

  • 删除进程描述符:在调用do_exit()之后,尽管线程已经僵死不能再运行了,但系统还保留了它的进程描述符,在父进程获得已终结的子进程的信息或通知内核它不关注那些信息后,子进程的task_struct结构才释放。
  • 孤儿进程造成的进退维谷:由于进程退出时需要父进程通知父进程释放子进程的task_struct,如果一个进程找不到父进程就会在退出时永远处于僵死状态。因此要在父进程退出时为每一个子进程找到一个新的父亲,方法是给子进程在当前线程组内找一个线程作为父亲,如果不行就让init做它们的父进程。

int
main(void){

  int
i;

  for(i=0;
i<2; i++){

    fork();

    printf(“_n”);}

  wait(NULL);wait(NULL);

  return
0;}

   答案是6个“_”,具体思路如图2所示。(i=0,调用一次fork后,父进程a创建子进程b,此后a和b进行打印,打印两个“_”;后i=1,a和b均调用fork,a创建子进程a_1,b创建子进程b_1,4个进程执行打印操作,打印出四个“_”;i=2,返回)

图片 2图2
fork解题思路

 

问题三:用户空间进程的页表是什么时候分配的,其中一级页表什么时候分配?二级页表呢?

   (此问有点疑问,暂且认为一级页表为页目录项(pgd),二级页表为也表项(pte))

  对于内核来说,进程的“鼻祖”是idle进程,称为swapper进程;对于用户空间来说,进程“鼻祖”是init进程,所有用户空间进程都由init进程创建或派生。

  在mm_init()函数中,首先给新进程的mm_struct数据结构进行初始化,然后对mm_users,mm_count进行初始化,设置进程空间地址读写信号量,设置保护进程页表的spinlock锁,最后调用pgd_alloc()函数进行pgd页表的分配工作。在pgd_alloc()函数中,调用pte_alloc_map()函数进行第0,第1个页表的分配,此后在dup_mmap()函数中将父进程所有的VMA对应的pte页表项复制到子进程对应的pte页表项中。

问题四:请简述fork,vfork和clone之间的区别?

   fork,vfork,clone的实现都是通过调用do_fork()函数实现的,只是函数调用不一样。

  fork函数实现:do_fork(SIGCHLD,0,0,NULL,NULL);只使用SIGCHLD标志位,在子进程终止后发送SIGCHLD信号通知父进程。fork是重量级调用,为子进程建立了一个基于父进程的完整副本,然后子进程基于此运行。为了减少工作量采用写时复制技术(COW),子进程只复制父进程的页表,不复制页面内容。当子进程需要写入新内容时,才触发写时复制机制,为子进程创建一个副本。

  vfork函数实现:do_fork(CLONE_VFORK | CLONE_VM |
SIGCHLD,0,0,NULL,NULL);它比fork多了两个标志位,分别为CLONE_VFORK和CLONE_VM。CLONE_VFORK表示父进程会被挂起,直至子进程释放虚拟内存资源。CLONE_VM表示父子进程运行在相同的内存空间中。

  clone函数实现:do_fork(clone_flags, newsp, 0, parent_tidptr,
child_tidptr);clone用于创建线程,并且参数通过寄存器从用户空间传递下来,通常会指定新的栈地址(newsp)。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 大奖888网页版登陆 版权所有