当前位置: 首页 > news >正文

linux进程管理-进程

目录

进程是什么?

进程描述符以及任务结构

分配进程描述符

进程描述符的存放

进程状态

进程的创建:

写时拷贝

fork()


进程是什么?

进程就是处于执行期的程序(目标码存放在某种存储介质上)。但进程并不仅仅局限于-一-段可执行程序代码(Unix称其为代码段,text section)。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程( thread of execution),当然还包括用来存放全局变量的数据段等。实际上,进程就是正在执行的程序代码的实时结果。内核需要有效而又透明地管理所有细节。

执行线程,简称线程(thread),是在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器内核调度的对象是线程,而不是进程。在传统的Unix系统中,一个进程只包含一个线程,但现在的系统中,包含多个线程的多线程程序司空见惯。稍后你会看到,Linux系统的线程实现非常特别:它对线程和进程并不特别区分。对Linux而言,线程只不过是一种特殊的进程罢了。

在现代操作系统中,进程提供两种虚拟机制﹔虚拟处理器和虚拟内存。虽然实际上可能是许多进程正在分享一个处理器,但虚拟处理器给进程一种假象,让这些进程觉得自己在独享处理器,虚拟内存让进程在分配和管理内存时觉得自己拥有整个系统的所有内存资源。有趣的是,注意在线程之间

无疑,进程在创建它的时刻开始存活。在Linux 系统中,这通常是调用fork()系统的结果,该系统调用通过复制一个现有进程来创建一个全新的进程。调用fork()的进程称为父进程,新产生的进程称为子进程。在该调用结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。可以共享虚拟内存,但每个都拥有各自的虚拟处理器。

创建新的进程都是为了立即执行新的、不同的程序,而接着调用exec()这组函数就可以创建新的地址空间,并把新的程序载入其中。在现代Linux内核中,fork()实际上是由clone0系统调用实现的,后者将在后面讨论。

最终,程序通过exit()系统调用退出执行。这个函数会终结进程并将其占用的资源释放掉。父进程可以通过wait4)系统调用查询子进程是否终结,这其实使得进程拥有了等待特定进程执行完毕的能力。进程退出执行后被设置为僵死状态,直到它的父进程调用wait()或waitpid)为止。

进程描述符以及任务结构

内核把进程的列表存放在叫做任务队列(task list)的双向循环链表中。链表中的每一项都是类型为task_struct、称为进程描述符( process descriptor)的结构,该结构定义在<linuxlsched.h>文件中。进程描述符中包含一个具体进程的所有信息。

分配进程描述符

slap分配动态创建一个struct thread_info

struct thread_info
{

struct task_struct        *task;
struct exec_domain        *exec_domain;
_u32                      flags;
_u32                      statues;
_u32                      cpu;
int                       preempt_count;
....
}

进程内核栈如下图:

 每个任务的thread_info结构在它的内核栈的尾端分配。结构中task域中存放的是指向该任务实际task_struct的指针。

进程描述符的存放

在内核中,访问任务通常需要获得指向其 task_struct的指针。实际上,内核中大部分处理进程的代码都是直接通过task_struct进行的。因此,通过current宏查找到当前正在运行进程的进程描述符的速度就显得尤为重要。硬件体系结构不同,该宏的实现也不同,它必须针对专门的硬件体系结构做处理。有的硬件体系结构可以拿出一个专门寄存器来存放指向当前进程task_struct的指针,用于加快访问速度。而有些像x86这样的体系结构(其寄存器并不富余),就只能在内核栈的尾端创建thread_info结构,通过计算偏移间接地查找task_struct结构。

进程状态

statusdescription
TASK_RUNNING进程是可执行的,或者是正在执行,或者在运行队列中等待执行。这是进程在用户空间内中执行的唯一状态
TASK_INTERRUPTIBLE进程正在睡眠(被block),可被唤醒到running态
TASK_UNINTERRUPTIBLE与之相反
TASK_TRACED被其他进程跟踪的进程
TASK_STOPPEND进程被停止进行

 进程上下文:

当一个程序调执行了系统调用,或者触发了某个异常,它就陷入了内核空间。此时,我们称内核“代表进程执行”并处于进程上下文中。

系统调用和异常处理程序是对内核明确定义的接口。进程只有通过这些接口才能陷入内核执行——对内核的所有访问都必须通过这些接口。

进程家族树

所有的进程都是PID为1的init进程的后代。内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本( initscript)并执行其他的相关程序,最终完成系统启动的整个过程。

系统中的每个进程必有一个父进程,相应的,每个进程也可以拥有零个或多个子进程。拥有同--个父进程的所有进程被称为兄弟。进程间的关系存放在进程描述符中。每个task_struct都包含一个指向其父进程tast_struct、叫做parent 的指针,还包含一个称为children 的子进程链表。所以,对于当前进程,可以通过下面的代码获得其父进程的进程描述符:

struct task_struct *my_parent=current->parent
//获取子进程
struct task_struct task;
struct list_head*list;

list_for_each(list,&current->children)
{
task=list_entry(list,struct task_struct,sibling);
}

进程的创建:

它首先,fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在于PID(每个进程唯一)、PPID(父进程的进程号,子进程将其设置为被拷贝进程的PID)和某些资源和统计量(例如,挂起的信号,它没有必要被继承)。exec()函数负责读取可执行文件并将其载入地址空间开始运行。把这两个函数组合起来使用的效果跟其他系统使用的单一函数的效果相似。

写时拷贝

只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候才进行。在页根本不会被写入的情况下〈举例来说,forkO后立即调用execO)它们就无须复制了。

fork()

Linux通过clone()系统调用实现 fork()。这个调用通过一系列的参数标志来指明父、子进程需要共享的资源。fork()、vfork()和__clone 库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用do_fork(),然后该函数调用啦copy_process()让进程去运行。

1))调用dup_task_struct(为新进程创建-一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。此时,子进程和父进程的描述符是完全相同的。

2)检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。

3)子进程着手使自己与父进程区别开来。进程描述符内的许多成员都要被清0或设为初始值。那些不是继承而来的进程描述符成员,主要是统计信息。task_struct中的大多数数据都依然未被修改。

4〉子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会投入运行。

5) copy_process()调用copy_flags()以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERPRIV标志被清0。表明进程还没有调用exec()函数的PF_FORKNOEXEC标志被设置。

6)调用alloc _pid0为新进程分配一个有效的PID。

7〉根据传递给clone()的参数标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。在一般情况下,这些资源会被给定进程的所有线程共享﹔否则,这些资源对每个进程是不同的,因此被拷贝到这里。
8)最后,copy_process(做扫尾工作并返回一个指向子进程的指针。

再回到do_fork()函数,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行9。因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。

相关文章:

  • 【图像透视】基于matlab图像逆透视映射【含Matlab源码 2139期】
  • 【Java】逻辑控制
  • Qt下生成pdb文件,并在exe崩溃时生成dmp文件,且由dmp查询崩溃原因
  • 蜂鸣器、风扇、震动马达
  • 【VRP问题】基于帝国企鹅优化算法求解冷链配送物流车辆调度优化研究
  • 3) 时频分析与傅立叶变换
  • stm32f4xx-I2C
  • 有了这个 Python 库,以后再也不用写正则表达式了
  • 学习python很无聊?看看这几个有意思的代码,拿去整蛊一下好朋友~ 适当娱乐哈
  • 【老生谈算法】matlab实现滤波器设计源码——滤波器设计
  • 后端研发工程师面经——手撕设计模式
  • 1679. K 和数对的最大数目-自定义哈希表解决
  • 【Objective-C内存管理之引用计数】
  • 找工作经验总结——嵌入式软件工程师必备的能力(表达篇)
  • 【vue基础篇】极简 ESLint + Prettier 配置教程
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【前端学习】-粗谈选择器
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Android系统模拟器绘制实现概述
  • Javascript设计模式学习之Observer(观察者)模式
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Linux各目录及每个目录的详细介绍
  • Markdown 语法简单说明
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Vue2.0 实现互斥
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 大整数乘法-表格法
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 前端技术周刊 2019-02-11 Serverless
  • 时间复杂度与空间复杂度分析
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 学习JavaScript数据结构与算法 — 树
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​你们这样子,耽误我的工作进度怎么办?
  • #控制台大学课堂点名问题_课堂随机点名
  • (23)Linux的软硬连接
  • (备忘)Java Map 遍历
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (三)c52学习之旅-点亮LED灯
  • (三)终结任务
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .Net6 Api Swagger配置
  • .NET中使用Redis (二)
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • @SuppressWarnings(unchecked)代码的作用
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票
  • [AAuto]给百宝箱增加娱乐功能
  • [BZOJ2850]巧克力王国