linux 内核笔记1-进程管理

  • clone()-fork()-exec()-exit() 子进程结束ZOMBIE 父进程wait4()
    进程描述符 task_struct进程所有信息 由thread_info分配。为了提高current宏找进程描述符的速度,方便使用偏移量计算进程描述符

  • 进程状态
    TASK_RUNNING
    TASK_INTERRUPTIBLE
    TASK_UNINTERRUPTIBLE
    TASK_ZOMBIE
    TASK_STOP

  • 用户空间——sys_Call /异常——(进程上下文)内核空间

  • copy-on-write 写时拷贝其他以只读方式共享 fork()分配子进程页表和task_struck. vfork()不拷贝页表,其余同fork,在无写时拷贝时代有利。如今相差无几

进程创建

Unix:fork() 和exec() 首先fork() 拷贝父进程,区别pid和资源和统计量。fork()实际开销是复制父进程的页表以及给子进程创建唯一的进程描述符。exec()读取可执行文件载入地址空间运行。

  • clone() -> do_fork() -> copy_process():
      1. dup_task_struck() 与父进程完全相同的内核栈 thread_info task_struct
      1. 检查总进程数量小于max
      1. 区分父子进程:task_struct 成员清零或初始值
      1. 子进程 TASK_UNINTERRUPIBLE 不被执行
      1. 更新进程描述符的flag成员 copy_flags() PF_SUPERPRIV=0 设置PF_FORKNOEXEC(未执行标志)
      1. 分配alloc_pid()
      1. 从clone()的输入参数中确定新进程行为方式和父子进程之间共享的资源种类 eg: clone(CLONE_VM| CLONE_FS | CLONE_SIGHAND,0)
      1. 扫尾,返回do_fork()指向子进程的指针

copy_process-复制父进程的内核栈进程描述符等-检查进程数量-进程描述符设置初始值-设置UNINTERRUPTBLE-更新flags-分配pid-分配共享资源-返回指针给do_fork()

  • 内核线程区别在于无独立地址空间 mm指针-> NULL 只在内核空间运行且由内核线程生成。 调用do_exit()退出,或内核其他部分调用kthread_stop()退出

进程终结

  • do_exit():
      1. 在进程描述符task_struct 标志成员PF_EXITING
      1. del_time_sync() 删除内核定时器
      1. 若有BSD 进程记账功能 输出信息
      1. exit_mm() 释放占用的mm_struct 若无其他进程使用此地址空间则彻底释放
      1. sem_exit() 释放信号
      1. exit_files() exit_fs() 递减引用计数,若某引用计数为0释放资源
      1. exit_code() 设为exit()的退出代码。供父进程检索
      1. exit_notify()告诉父进程,给子进程找养父。进程状态设为EXIT_ZOMBIE
      1. do_exit()调用schedule()切换到其他进程。不返回
  • 此时占有的内存为内核栈、task_struct、thread_info结构。此时进程存在的意义是向他的父进程提供信息。待父进程释放。

do_exit()-进程描述符设置退出标志-释放地址空间-释放信号队列-释放文件计数器-退出代码-告父,子进程找养父-EXIT_ZOMBIE

删除进程描述符

由父进程释放, wait() 族调用唯一的系统调用wait4()函数来实现。wait4挂起调用它的进程,直到其中一个子进程退出,返回该子进程的PID。 调用wait4函数的指针包含了子函数的退出代码。
释放进程描述符时,release_task()函数会被调用完成以下工作

    1. 调用 _exit_signal() -> _unhash_process() ->detach_pid()从pidhash中删除该进程,任务列表中也删除
    1. _exit_signal()释放目前僵死进程所有剩余资源。统计记录
    1. 调用put_task_struct()释放进程内核栈和thread_info所占的页,释放task_struct所占的slab高速缓存
孤儿进程

父进程在子进程之前退出,不找养父则子进程退出一直是僵死态,占用内存。
解决方法:当前线程组找新父亲,不行则用init进程。exit_notify()-> forget_original_parent()->find_new_reaper()
寻父

*find_new_reaper(*father){
  *pid_ns=(father);
 task_struct *thread =father;
 while_each_thread(father,thread){
        if(flags & PF_EXITING)
         /*若标识符和被设为退出状态 则跳过*/
         if(unlikely(pid_ns->child_reaper==father))
            pid_ns->child_reaper=thread;
            /*线程组有其他线程*/
            return thread;
        }
       if(unlikely(pid_ns->child_reaper==father)){
          获得tasklist_lock写锁
          /*设置init进程为继父*/
          if(unlikely(pid_ns==&init_pid_ns))
     
            zap_pid_ns_processes(pid_ns);
            tasklist_lock 上锁
            pid_ns->child_reaper=init_pid_ns.child_reaper;
       }
       return pid_ns->child_reaper;
    }

遍历所有子进程设置新的父进程

reaper=find_new_reaper(father);
list_for_each_entry_safe(p,n,&father->children,sibling){
    p->real_parent=reaper;   //real_parent 是继父
    if(p->parent==father){
        BUG_ON(p->ptrace);
        p->parent=p->real_parent;
    }
    reparent_thread(p.father);
}

然后调用ptrace_exit_finish()同样寻父,给ptraced的子进程寻父

void exit_ptrace(struct task_struct *tracer){
    struct task_struct *p, *n;
    LIST_HEAD(ptrace_dead);
     // 获得tasklist_lock写锁
    list_for_each_entry_safe(p,n,&tracer->ptraced ,ptrace_entry){//子进程列表
    if(_ptrace_detach(tracer,p))
        list_add(&p->ptrace_entry.&ptrace_dead);
     //  释放tasklist_lock 锁
     }
       BUG_ON(! list_empty(&tracer->ptraced));
           list_for_each_entry_safe(p,n,&ptrace_dead ,ptrace_entry){// ptrace子进程列表
                    list_del_init(&p->ptrace_entry);
                    release_task(p);
        }
}

当一个进程被跟踪时,他的临时父亲设定为调试进程。此时他的父进程退出了,系统会为它和它的所有兄弟进程重新找一个父进程。之前的内核会遍历系统所有进程来找这些子进程。现在的解决方法是在一个单独被ptrace跟踪的子进程链表中搜索相关的兄弟进程。用两个相对较小的表减轻遍历的消耗。
init进程例行调用wait()检查进程。清除僵死进程。

小结

copy_process - 复制父进程的内核栈进程描述符等 - 检查进程数量 - 进程描述符设置初始值 - 设置UNINTERRUPTBLE - 更新flags - 分配pid - 分配共享资源 - 返回指针给do_fork()
do_exit() - 进程描述符设置退出标志 - 释放地址空间 - 释放信号队列 - 释放文件计数器 - 退出代码 - 告父,子进程找养父 - EXIT_ZOMBIE
linux
如何存放和表示进程(task_struct和 thread_info)
如何创建进程 (通过fork()实际上最终是clone())
如何把新的执行映像装入地址空间(通过exec()系统调用族)
进程如何消亡(强制或自愿的调用exit())

发布了27 篇原创文章 · 获赞 1 · 访问量 672

猜你喜欢

转载自blog.csdn.net/SUKI547/article/details/103990072