• 深入理解linux内核-进程和程序


    进程描述符task_struct

     task_struct

    {

      //进程基本信息

      pid 进程id号

      tgid 线程组id号,与线程组领头线程pid号相同   getpid()返回该值

      tasks init_struct链接所有task_struct结构

      run_list; //当前进程所处的运行链表

      array 指向与进程相关的prio_array_t结构

      real_parent当前进程的父进程,没有的话将会变成进程1(init)的描述符

      parent  被执行跟踪时的跟踪父进程(ptrace)

      children  链接所有子进程

      sibling  兄弟进程链表

      group_loader  进程组领头进程的描述符指针

      signal->pgrp  进程组领头进程的PID

      signal->session  登录会话领头进程的pid

      ptrace_children  所有被本进程跟踪的子进程链表

      ptrace_list    指向所跟踪进程其实际父进程的链表的前一个和下一个元素????

      pid pids[4];    //四个结构用于查找指定 进程id、进程组id、线程组id、会话id。每个结构用于分别保存相同散列值的相同或不同pid列表

      //资源限制

      signal->rlim[rLIMIT_CPU...].rlim_cur;  //当前资源限制

      signal->rlim[rLIMIT_CPU...].rlim_max;  //普通用户最大权限

      //进程切换

      thread_struct thread;//保存进程切换时内核硬件上下文

      进程优先级

      进程运行状态(可运行、可中断等待、不可中断等待、暂停、跟踪、僵死、僵死撤销)

      //进程地址空间 mm_struct

      //当前目录 fs_struct

      //进程访问文件 files_struct

      //所接收的信号 signal_struct

      //相关的tty  tty_struct

    }

    不可中断状态:驱动在进行一些不能被中断的操作,因此进程处于该状态。

    暂停状态:进程收到SIGSTOP SIGTSTP SIGTTIN SIGTTOU进入暂停状态

    跟踪状态:进程执行处于被debugger程序暂停的状态

    僵死状态:进程执行终止,等待返回进程信息时。

    僵死撤销状态:为防止多线程同时等待进程终止时的信息,信息被获取后的状态

    线程描述符thread_info

    thread_info与内核态堆栈放在一起占用一个(4K)或两个页(8k)(用户态堆栈不在这里)

    内核态时可以通过堆栈寄存器获取当前thread_info结构的地址。

     thread_info

    {

      cpu//当前CPU

    }

    prio_array_t

    {

      int nr_active;        链表中进程描述符的数量

      unsigned long[5] bitmap;    当某个优先权链表不为空时对应位为1

      struct list_head[140]  queue;  140个优先权队列

    }

    将不同优先权的进程排入不同链表

    进程组和线程组的概念

    进程组:表示一个作业(job),例如 ls|sort|more三个进程处于一个进程组

    进程组:进程描述符中signal->pgrp相同的所有进程处于一个进程组

    线程组:进程描述符中tgid相同的所有进程处于一个线程组。

    getpid() kill() _exit()对线程组整体起作用。

    线程组所有成员死亡后才会产生一个信号通知线程组的领头进程的父进程

    通过pid快速查找进程

    为了能快速找到对应进程描述符,内核引入四个散列表(保存在pid_hash数组)

    进程pid散列表

    线程组tgid散列表

    进程组pgrp散列表

    会话session散列表

    pid

    {

      int nr;//对应类型的pid数值

      struct hlist_node pid_chain;//相同散列值但pid不同的链表 链接pid结构

      struct list_head  pid_list;//相同散列值相同pid的进程双向链表  链接pid结构

    }

    对于每一个散列表,进程描述符有一个pid数据结构对应

    进程的组织

    运行状态有对应链表

    停止、僵死、僵死撤销 状态没有对应的链表

    可中断等待和不可中断等待有多种独立的等待队列

    等待队列

    struct __wait_queue_head

    {

      spinlock_t lock;    //自旋锁

      struct list_head task_list;//非互斥进程从第一个位置放,互斥进程放在最后一个

    }

    wait_queue_t

    {

      unsigned int flags;//1表示等待队列是互斥资源的访问,0等待队列是非互斥资源

      struct task_struct *task;  //对应的进程描述符

      wait_queue_func_t func;//表示等待队列的唤醒函数

      struct list_head list;//所有排入等待队列的wait_queue_t

    }

    sleep_on类函数在一些条件不能使用:必须测试条件并且当条件还没得到验证时又紧接着让进程去睡眠????

    sleep_on(wait_queue_head_t)  (非互斥:一旦条件满足则所有非互斥等待都会唤醒!!!)

    interruptible_sleep_on()      (非互斥)

    sleep_on_timeout()        (非互斥)

    interruptible_sleep_on_timeout()  (非互斥)

    prepare_to_wait()       //(非互斥)需要自己调用schedule()或者schedule_timeout()

    prepare_to_wait_exclusive()  //(互斥)需要自己调用schedule()或者schedule_timeout()

    finish_wait()

    唤醒函数

    wake_up           不带nr和all则只唤醒一个互斥进程

    wake_up_nr          nr代表唤醒互斥进程的数量

    wake_up_all          all代表唤醒所有互斥进程

    wake_up_interruptible      interruptible代表只唤醒可中断睡眠,不带该后缀唤醒两种睡眠

    wake_up_interruptible_nr    

    wake_up_interruptible_all

    wake_up_interruptible_sync    sync代表如果唤醒 进程优先级更高**不会**立即执行该高优先级进程

    wake_up_locked        当等待队列中的自旋锁已经被持有时使用

    进程资源限制

    地址空间最大数        RLIMIT_AS

    内存信息转储空间大小     RLIMIT_CORE

    进程使用CPU的最长时间    RLIMIT_CPU

    堆大小的最大值        RLIMIT_DATA

    文件大小最大值        RLIMIT_FSIZE

    文件锁数量最大值       RLIMIT_LOCKS

    非交换内存的最大值      RLIMIT_MEMLOCK

    消息队列中的最大字节数    RLIMIT_MSGQUEUE

    打开文件描述符的最大数    RLIMIT_NOFILE

    用户拥有进程最大数      RLIMIT_NPROC

    进程拥有页框最大数      RLIMIT_RSS

    进程挂起信号的最大数       RLIMIT_SIGPENDING

    栈大小的最大数        RLIMIT_STACK

    进程切换

    thread_struct

    {

      eip//进程恢复执行后需要执行的首地址(保存+加载)

      esp//进程切换时内核态指针(保存+加载)

      esp0//内核态初始指针(仅加载)

      tls_array[3];//线程局部存储段(仅加载)

      fs  gs;段寄存器(保存+加载)

      debugreg;调试寄存器dr0-dr3 dr6-dr7(仅加载)

      io_bitmap_ptr//表示IO权限位图是否有数据

    }

    硬件上下文:进程恢复执行前必须装入寄存器的一组数据

    硬件上下文的一部分放在TSS段中,剩余部分在内核态堆栈中

    可执行上下文:进程执行时需要的所有信息

     可执行上下文包含硬件上下文

    进程切换只发生在内核态,在切换之前,用户态进程使用的所有寄存器内容已保存在内核态堆栈上(包括用户态堆栈信息)

    任务状态段(TSS):保存内核态堆栈地址,检查in out指令执行时是否有IO许可权,linux中每个CPU只有一个TSS段

    thread_struct:在任务描述符中在进程切换时保存内核硬件上下文(包含大部分CPU寄存器,不包括eax、ebx这些通用寄存器(这些在内核堆栈中))

    switch_to函数执行步骤(主要堆栈切换、执行指针切换)

    1.将prev和next分别存入eax和edx防止堆栈切换导致指针变化

    2.保存需要保存的寄存器信息eflag和ebp(pushfl ;pushl ebp)

    3.将原堆栈指针esp保存在prev的结构中

    4.从next中将新堆栈指针写到esp中

    5.将原进程恢复后需要执行的地址存入prev中

    6.将新进程next的eip压入到新进程的栈中(栈已经切换完成)

    7.调用__switch_to,该函数引用eax和edx获得两个进程的其他硬件上下文并进行切换(FPU、MMX、XMM寄存器)

    8.恢复堆栈中的寄存器eflag和ebp

    9.从eax寄存器拷贝到last变量(本次进程切换被切出的进程描述符地址)

    __switch_to函数执行步骤(引用eax和edx获得本次切出和切入进程的进程描述符,栈顶保存了需要恢复执行的地址)

    1.__unlazy_fpu()保存切出进程的FPU、MMX和XMM寄存器

    2.获得cpu下标(内核栈切换完毕,通过栈指针找到thread_info,内部的cpu字段)

    3.将thread.esp0装入对应本地CPU的TSS的esp0字段(用户态切内核态后内核态的堆栈指针)

    4.装入线程局部存储段(TLS)thread.tls_array[0-2]

    5.保存原线程的fs、gs

    6.加载新线程的fs、gs(实际可能会产生无效的段寄存器值异常,并触发修正!!!!)

    7.加载6个调试寄存器dr0-dr3 dr6-dr7

    8.根据io_bitmap_ptr,懒惰模式设置iobitmap,需要时会产生异常,然后更新。

    9.返回值为eax被切出的进程描述符,返回的执行地址为栈顶的标号1地址

    考虑进程A的切出和切入

    prev、next、last为局部变量(保存在堆栈中)

    last用于返回被切出的进程描述符

    切出A          切入A

    进程A切换为进程B           进程C切换成进程A

    A      B      C      A

    prev=A   =>   prev=B    prev=C  =>  prev=A

    next=B   =>   next=other    next=A =>  next=B

    eax=prev => last=eax=A         eac=prev=C      last=eax=C

    第一列和第四列能看出,切出是的堆栈状态和切入时相同

    创建进程

     轻量级进程:共享页表、打开文件表、信号处理。

    vfork:使用clone实现,指定SIG_CHLD信号 flag为CLONE_VM CLONE_VFORK,堆栈为当前堆栈

    //创建的子进程和父进程共享内存地址空间,父进程在子进程退出或运行一个新的程序前阻塞。clone  

    fork:使用clone实现,子进程结束给父进程发送SIG_CHLD信号,clone标志为0,堆栈为当前堆栈(依赖写时复制机制可以同时运行)

    clone:(需要传递进程函数、参数、新的堆栈、线程局部存储段(TLS)、ptid、ctid)

    CLONE_VM        共享页表

    CLONE_FS         共享根目录和当前工作目录umask、 不能和CLONE_NEWNS同时设置!!!

    CLONE_FILES        共享打开文件

    CLONE_SIGHAND       共享信号处理 必须共享内存描述符CLONE_VM!!!

    CLONE_PTRACE      共享被调试状态、

    CLONE_VFORK      VFORK?????

    CLONE_PARENT   共享父进程、

    CLONE_THREAD  共享线程组 必须共享信号CLONE_SIGHAND!!!

    CLONE_NEWNS    新建命名空间  不能和CLONE_FS同时设置!!!

    CLONE_SYSVSEM    共享IPC取消信号量操作、

    CLONE_SETTLS    新建TLS(局部存储段)

    CLONE_PARENT_SETTID  将子进程PID返回给父进程ptid、?????????

    CLONE_CHILD_CLEARTID  子进程退出或执行新程序时清除指定变量ctid、?????

    CLONE_UNTRACED      禁止内核线程跟踪进程、

    CLONE_CHILD_SETTID    将字进程PID返回给子进程ptid、??????????

    CLONE_STOPPED      子进程默认停止状态

    内核线程

    使用dofork实现,CLONE_VM、CLONE_UNTRACED

    进程0

    从无到有创建的内核线程,初始化内核需要的所有数据结构,每个CPU都会启动一个进程用于空闲时运行,启动进程1

    进程1

    完成内核初始化,调用execve系统调用装入可执行程序init,变为普通进程,拥有自己的每进程内核数据结构???。

    进程克隆过程????暂时不总结

    3.5进程终止、进程删除?????暂时不总结

    几个进程能并发地执行同一个程序,而同一个进程能顺序的执行几个程序??????

  • 相关阅读:
    tomcat server.xml 配置示例
    Vue学习1:实例及生命周期
    flex布局
    从输入一个URL到页面完全显示发生了什么?
    webstorm配置eslint【标记错误,修复错误】
    JavaScript实现八大内部排序算法
    es6(六):module模块(export,import)
    es6(五):class关键字(extends,super,static)
    es6(四):Symbol,Set,Map
    es6(三):es6中函数的扩展(参数默认值、rest参数、箭头函数)
  • 原文地址:https://www.cnblogs.com/fanguang/p/8638387.html
Copyright © 2020-2023  润新知