zoukankan      html  css  js  c++  java
  • liunx中的进程与线程

    1. 进程和线程

      进程和线程是程序运行时状态,是动态变化的,进程和线程的管理操作(比如,创建,销毁等)都是有内核来实现的。

    Linux中的进程于Windows相比是很轻量级的,而且不严格区分进程和线程,线程不过是一种特殊的进程。

    所以下面只讨论进程,只有当线程与进程存在不一样的地方时才提一下线程。

    进程提供2种虚拟机制:虚拟处理器和虚拟内存

      每个进程有独立的虚拟处理器和虚拟内存,

      每个线程有独立的虚拟处理器,同一个进程内的线程有可能会共享虚拟内存。

    内核中进程的信息主要保存在task_struct中(include/linux/sched.h)

    进程标识PID和线程标识TID对于同一个进程或线程来说都是相等的。

    Linux中可以用ps命令查看所有进程的信息:

        ps -eo pid,tid,ppid,comm

    需要多线程原因:
      许多应用中同时发生着多种活动,某些活动会随着时间的推移被阻塞。通过将应用程序分解成可以准并运行的多个顺序
      线程可以使程序设计变得更加简单。
      线程比进程更加轻量化,比进程更加容易创建,更加容易撤销。
      如果存在大量的计算和i/o处理,拥有多个线程允许这些活动彼此重叠进行,从而加快应用程序的执行速度
    在同一进程中可以并行运行多个线程,他们共享同一地址空间和其他资源。

    
    

    2. 进程的创建

    Linux中创建进程与其他系统有个主要区别,Linux中创建进程分2步:fork()和exec()。

    fork: 通过拷贝当前进程创建一个子进程

    exec: 读取可执行文件,将其载入到内存中运行

    创建的流程:

    1. 调用dup_task_struct()为新进程分配内核栈,task_struct等,其中的内容与父进程相同。
    2. check新进程(进程数目是否超出上限等)
    3. 清理新进程的信息(比如PID置0等),使之与父进程区别开。
    4. 新进程状态置为 TASK_UNINTERRUPTIBLE
    5. 更新task_struct的flags成员。
    6. 调用alloc_pid()为新进程分配一个有效的PID
    7. 根据clone()的参数标志,拷贝或共享相应的信息
    8. 做一些扫尾工作并返回新进程指针

    创建进程的fork()函数实际上最终是调用clone()函数。

    创建线程和进程的步骤一样,只是最终传给clone()函数的参数不同。

    比如,通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0)

    创建一个和父进程共享地址空间,文件系统资源,文件描述符和信号处理程序的进程,即一个线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)

    在内核中创建的内核线程与普通的进程之间还有个主要区别在于:内核线程没有独立的地址空间,它们只能在内核空间运行。

    这与之前提到的Linux内核是个单内核有关。

    3. 进程的终止

    和创建进程一样,终结一个进程同样有很多步骤:

    子进程上的操作(do_exit)

    1. 设置task_struct中的标识成员设置为PF_EXITING
    2. 调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和运行
    3. 调用exit_mm()释放进程占用的mm_struct
    4. 调用sem__exit(),使进程离开等待IPC信号的队列
    5. 调用exit_files()和exit_fs(),释放进程占用的文件描述符和文件系统资源
    6. 把task_struct的exit_code设置为进程的返回值
    7. 调用exit_notify()向父进程发送信号,并把自己的状态设为EXIT_ZOMBIE
    8. 切换到新进程继续执行

    子进程进入EXIT_ZOMBIE之后,虽然永远不会被调度,关联的资源也释放掉了,但是它本身占用的内存还没有释放,
    比如创建时分配的内核栈,task_struct结构等。这些由父进程来释放。

    父进程上的操作(release_task)

    父进程受到子进程发送的exit_notify()信号后,将该子进程的进程描述符和所有进程独享的资源全部删除。

    从上面的步骤可以看出,必须要确保每个子进程都有父进程,如果父进程在子进程结束之前就已经结束了会怎么样呢?

    子进程在调用exit_notify()时已经考虑到了这点。

    如果子进程的父进程已经退出了,那么子进程在退出时,exit_notify()函数会先调用forget_original_parent(),然后再调用find_new_reaper()来寻找新的父进程。

    find_new_reaper()函数先在当前线程组中找一个线程作为父亲,如果找不到,就让init做父进程。(init进程是在linux启动时就一直存在的)

    4、 进程的状态

    a/ 运行态  : 进程实际占用cpu

    b/ 就绪态  : 可运行,但因为其他程序正在运行而暂时停止,没有占用cpu

    c/ 阻塞态  : 除非某种外部事件发生,否则进程不能运行

    前两种状态逻辑上类似,只是对于第二种状态暂时没有cpu分配给他。第三种状态不能运行即使cpu空闲也不行。

                                           

      运行与就绪的转换由进程调度程序引起的,调度程序的主要工作就是决定应当运行那个进程、何时运行及它该运行多长时间。

      就绪与运行: 在该时间片内运行完成,到就绪状态(队列)等待下一个时间片




  • 相关阅读:
    MFC知识点总结
    fopen函数打开文件总是返回NULL错误
    四.Windows I/O模型之重叠IO(overlapped)模型
    三.Windows I/O模型之事件选择(WSAEventSelect )模型
    二.Windows I/O模型之异步选择(WSAAsyncSelect)模型
    6.openldap客户端安装
    5.openldap设置用户本身修改密码
    4.openldap创建索引
    3.openldap生成LDAP用户
    2.openldap安装
  • 原文地址:https://www.cnblogs.com/NeilZhang/p/5606318.html
Copyright © 2011-2022 走看看