linux下进程总结

一. 概述

定义:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进程。

编写出来的c文件称为源码(对于人来说可读性高,但计算机无法识别),通过编译生成CPU可识别的二进制可执行程序保存在存储介质上(一般为外存),注意此时生成的是程序而不是进程。一旦开始运行该程序,则正在运行的该程序及其占用的资源就称为进程了。

进程的概念是针对系统的,而不是针对用户的。

进程会占用四类资源:

  • CPU (Central Processing Unit 中央处理单元 )lscpu命令可以看到CPU的详细信息
  • Memory(内存)free -h命令可以查看系统内存大小
  • Disk(磁盘)
  • Network(网络)
     

在传统的操作系统中,程序不可以独立的运行,作为资源分配和独立运行的基本单位都是进程

一个程序可以执行多次,这也意味着多个进程可以执行同一个程序。

二. 进程空间内存分布

Linux 进程内存管理的对象都是虚拟内存

每个进程先天就有 0-4G 的各自互不干涉的虚拟内存空间。0—3G 是用户空间执行用户自己的代码, 高 1GB 的空间是内核空间执行 Linu x 系统调用,这里存放整个内核的代码和所有的内核模块,用户所看到和接触的都是该虚拟地址,并不是实际的物理内存地址

Linux下,一个进程在内存里有三部分的数据,就是”代码段”、”堆栈段”和”数据段”,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分是构成一个完整的执行序列的必要的部 分。

“代码段”:存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。

“堆栈段”:存放的就是子程序的返回地址、子程序的参数以及程序的局部变量和malloc()动态申请内存的地址。

“数据段”:存放程序的全局变量,静态变量及常量的内存空间。

  •  :静态栈,在栈中变量的值为随机值。栈内存由编译器在程序编译阶段完成,进程的栈空间位于进程用户空间的顶部并且是向下增长,每个函数的每次调用 都会在栈空间中开辟自己的栈空间,函数参数、局部变量、函数返回地址等都会按照先入者为栈顶的顺序压入函数栈中, 函数返回后该函数的栈空间消失,所以函数中返回局部变量的地址都是非法的。
  • :动态堆,堆内存是在程序执行过程中分配的,用于存放进程运行中被动态分配的的变量,大小并不固定,堆位于非初始化数据 段和栈之间,并且使用过程中是向栈空间靠近的。当进程调用 malloc 等函数分配内存时,新分配的内存并不是该函数的栈帧中,而是被动态添加到堆上,此时堆就向高地址扩张;当利用 free 等函数释放内存时,被释放的内存从堆中被踢 出,堆就会缩减。因为动态分配的内存并不在函数栈帧中,所以即使函数返回这段内存也是不会消失。
  • 未初始化数据段(.bss):用来存放未初始化的全局变量和 static 静态变量。并且在程序开始执行之前, 就是在 main()之前,内核会将此段中的数据初始化为 0 或空指针。
  • 已初始化数据段(.data):用来保已初始化的全局变量和 static 静态变量。
  • 文字常量区(.rodata):存放文字常量,如char * str = "hello";  中的hello。
  • 文本段(代码段):这是可执行文件中由 CPU 执行的机器指令部分。正文段常常是只读的,以防止程序由于意外而修改其自身的执行。

PS :  linux中的内存分配

Linux 内存管理的基本思想就是只有在真正访问一个地址的时候才建立这个地址的物理映射。

Linux C/C++语言的分配方式共有 3 种方式:

(1)从静态存储区域分配。就是数据段的内存分配,这段内存在程序编译阶段就已经分配好,在程序的整个运行期间都存在, 例如全局变量,static 变量。

(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。 栈内存分配运算内置于处理器的指令集中,效率很高,但是系统栈中分配的内存容量有限,比如大额数组就会把栈空间撑爆导致 段错误。

(3)从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多,比如指向某个内存块的指针取值发生了变化又没有其他指针指向这块内存,这块内存就无法访问,发生内存泄露。

三. 进程的特征


1. 结构特征
进程通常是不可以并发执行的,为使程序独立运行,必须配置进程控制块(PCB),由程序段、数据段和PCB共同组成进程实体,我们通常所说的进程就是进程实体,创建进程就是创建进程实体中的PCB。

2. 动态性
进程的实质就是进程实体的一次执行过程,因此动态性是进程最基本的特征,具体表现在:“由创建而产生、调度而执行、撤销而消亡”。

3. 并发性
指的是多个进程实体共同存在内存中且可以在一段时间内同时运行,并发性是进程的重要特征,也是操作系统重要特征,引入进程目的就是为了并发性。

4. 独立性
进程实体是一个独立运行、独立分配资源和独立接受调度的基本单元。

5. 异步性
进程实体按异步方式运行

 

四. 进程的基本状态

进程其基本状态有5种,即创建状态、就绪状态、执行状态、阻塞状态、终止状态

常见的为就绪、执行、阻塞三种。

  • 创建: 进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。
  • 就绪: 进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行。
  • 执行: 进程处于就绪状态被调度后,进程进入执行状态。
  • 阻塞: 正在执行的进程由于某些事件而暂时无法运行,进程受到阻塞。
  • 终止: 进程结束,或出现错误,或被系统终止,进入终止状态,无法再执行。

进程状态是指一个进程的生命周期可以划分为一组状态,这些状态刻画了整个进程,进程状态即体现一个进程的生命状态。

五. 进程终止

linux下有6种终止进程的方式:

1. 正常返回:

(1)从main函数返回;

(2)调用exit,_exit,_Exit 函数;

(3)最后一个线程从启动历程返回或调用pthread_exit函数;

2. 异常返回:

(1)调用abort函数;

(2)接到一个信号;

(3)最后一个线程对取消请求做出响应。

无论进程如何终止,内核都会关闭所有打开的文件描述符,释放他们所使用的资源。

六. 查看系统当前进程

linux在终端中输入 ps aux 以查看当前时间节点进程信息。

此外,用命令如 ps aux | grep xiao 可查找xiao用户所使用的进程。

ps le \ps ef  ( l表示显示详细信息,e表示显示所有进程 )。

  • USER:该进程是由那个用户产生的
  • PID:进程的ID编号
  • %CPU:该进程的CPU资源占用百分比
  • %MEM:该进程的内存资源占用百分比
  • VSZ:该进程的虚拟内存的大小,单位KB(将磁盘的一部分空间转为虚拟内存使用,在物理内存使用占满后才会用到)
  • RSS:该进程占用实际物理内存的大小,单位KB
  • TTY:该进程是在哪个终端上运行的(TTY1~TTY6代表本地控制台终端。TTY1是图形终端,TTY2~6是本地的字符界面终端。PTS/0-255代表虚拟终端。)
  • STAT:进程状态。R:运行、S:睡眠、T:停止、s:包含子进程、+:位于后台
  • START:该进程启动时间
  • TIME:该进程占用系统得到运算时间(注意不是系统时间)
  • COMMAND:产生此进程的命令名

 此外,输入 top ,表示进入监听模式。进入监听模式后,可输入以下命令进行操作:

  • h:显示帮助;
  • P:以cpu进行排序;
  • M:以内存排序;
  • N:以PID排序;
  • q:可以退出;

该命令可以动态显示进程的信息变化。

七. 一些进程相关重要知识点

1. init进程

Linux内核启动的最后阶段会创建init进程来执行程序/sbin/init,该进程是系统运行的第一个进程,进程号为 1,称为 Linux 系统的初始化进程,该进程会创建其他子进程来启动不同写系统服务,而每个服务又可能创建不同的子进程来执行不同的 程序。所以init进程是所有其他进程的“祖先”,并且它是由Linux内核创建并以root的权限运行,并不能被杀死。Linux 中维护 着一个数据结构叫做 进程表,保存当前加载在内存中的所有进程的有关信息,其中包括进程的 PID(Process ID)、进程的状态、 命令字符串等,操作系统通过进程的 PID 对它们进行管理,这些 PID 是进程表的索引。

2. 子进程从父进程中所继承的东西

子进程得到的是父进程资源的拷贝,而不是他们本身。

(1)子进程从父进程中继承到:

  • 进程的资格( 真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和租号(GIDs))
  • 环境(environment)变量
  • 堆栈
  • 内存
  • 打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)
  • 执行时关闭(close-on-exec)标志(译者注: close-on-exec标志可通过fnctl()对文件描述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明,参见《APUE》W.R. Stevens, 1993,尤晋元等译(以下简称《高级编程》).3.13节和8.9节)
  • 信号(signal)控制设定
  • nice值(nice值由nice函数设定,值表示进程的优先级,数值越小,优先级越高)
  • 进程调度类别(scheduler class)(译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
  • 进程组号
  • 对话期ID(Session lD)(译者注:译文取自《高级编程》,指:进程所属的对话期(session)lD,一个对话期包括一个或多个进程组,更详细说明参见《APUE》 9.5节)
  • 当前工作目录
  • 根目录(根目录不—定是"/",它可由chroot函数改变)
  • 文件方式创建屏蔽字(file mode creation mask (umask))
  • 资源限制
  • 控制终端

(2)子进程独有

  • 进程号
  • 不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同,父进程号可由getppid函数得到)
  • 自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
  • 子进程不继承父进程的进程,正文(text),数据和其它锁定内存(memory locks)(译者注:锁定内存指被锁定的虚拟内存页,锁定后,不允许内核将其在必要时换出(page out),详细说明参见《The GNU C Library Reference Manual》2.2版,1999,3.4.2节)
  • 在tms结构中的系统时间(译者注: tms结构可由times函数获得,它保存四个数据用于记录进程使用中央处理器(CPU;Central Processing Unit)的时间,包括:用户时间,系统时间,用户各子进程合计时间,系统各子进程合计时间)
  • 资源使用(resource utilizations)设定为0
  • 阻塞信号集初始化为空集(译者注:原文此处不明确,译编辑fork函数手册页稍做修改)
  • 不继承由timer_create函数创建的计时器
  • 不继承异步输入和输出
  • 父进程设置的锁(因为如果是排他锁,被继承的话就矛盾了)
     

 3. 系统限制

一个服务器不可以给无限多个客户端提供服务,在Linux下每种资源都有相关的软硬限制,譬如单个用户最多能创建的子进程个数有限制,同样一个进程最多能打开的文件描述符也有相应的限制值,这些限制会限制服务器能够提供并发访问的客户端的数量。

在linux下可以使用getrlimit()和setrlimit()两个函数来获取或设置这些限制:

#include   <sys/resource.h>


int getrlimit(  int resource,struct rlimit *rlim);          //获取限制
int setrlimit(  int resource,   const struct rlimit *rlim);   //设置限制


(1)参数resource说明:

  • RLIMIT_AS          //进程的最大虚内存空间,字节为单位。
  • RLIMIT_CORE    //内核转存文件的最大长度。
  • RLIMIT_CPU       //最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。
  • RLIMIT DATA       //进程数据段的最大值。
  • RLIMIT_FSIZE    //进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
  • RLIMIT_LOCKS   //进程可建立的锁和租赁的最大值。
  • RLIMIT_MEMLOCK       //进程可锁定在内存中的最大数据量,字节为单位。RLIMIT_MSGQUEUE    //进程可为POSIX消息队列分配的最大字节数
  • RLIMIT_NICE       //进程可通过setpriority)或nice(调用设置的最大完美值。
  • RLIMIT_NOFILE  //指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
  • RLIMIT_NPROC   //用户可拥有的最大进程数。
  • RLIMIT_RTPRIO  //进程可通过sched_setscheduler和sched_setparam设置的最大实时优先级。
  • RLIMIT SIGPENDING   //用户可拥有的最大挂起信号数。
  • RLIMIT_STACK    //最大的进程堆栈,以字节为单位。

(2)rlim:描述资源软硬限制的结构体

        struct rlimit {
                rlim_t rlim_cur;

                rlim_t rlim_max;

        };

一个服务器程序抛开硬件(CPU、内存、带宽)限制以外,还会受到Linux系统的资源限制。所以,如果我们想要增 加Linux服务器并发访问的客户端数量,则需要在服务器程序里通过调用setrlimit()函数来修改这些限制。 

八. 一些进程相关常用函数

1. Linux下有两个基本的系统调用可以用于创建子进程:fork()和vfork()。

(1)fork()系统调用

由于fork()系统调用会创建一个新的进程,这时它会有两次返回。一次返回是给父进程,其返回值是子进程的PID(Process ID),第二次返回是给子进程,其返回值为0。

我们在调用fork()后,需要通过其返回值来判断当前的代码是在父进程还是子进程运行。

返回值为0:子进程

返回值>0:父进程

返回值<0:fork()系统调用出错

fork 函数调用失败的原因主要有两个:

①系统中已经有太多的进程;

② 该实际用户 ID 的进程总数超过了系统限制。

每个子进程只有一个父进程,并且每个进程都可以通过getpid()获取自己的进程PID,也可以通过getppid()获取父进程的 PID,这样在fork()时返回0给子进程是可取的。一个进程可以创建多个子进程,这样对于父进程而言,他并没有一个API函数可 以获取其子进程的进程ID,所以父进程在通过fork()创建子进程的时候,必须通过返回值的形式告诉父进程其创建的子进程PID。 这也是fork()系统调用两次返回值设计的原因。

注意:

fork()系统调用会创建一个新的子进程,这个子进程是父进程的一个副本。这也意味着,系统在创建新的子进程成功后,会将 父进程的文本段、数据段、堆栈都复制一份给子进程,但子进程有自己独立的空间,子进程对这些内存的修改并不会影响父进程 空间的相应内存。这时系统中出现两个基本完全相同的进程(父、子进程),这两个进程执行没有固定的先后顺序,哪个进程先执 行要看系统的进程调度策略。如果需要确保让父进程或子进程先执行,则需要程序员在代码中通过进程间通信的机制来自己实 现。

(2)vfork()系统调用
vfork)是另外一个可以用来创建进程的函数,他与fork()的用法相同,也用于创建一个新进程。

vfork()并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit(),于是也就不会引用该地址空间了。不过子进程再调用exec(或exit()之前,他将在父进程的空间中运行,但如果子进程想尝试修改数据域(数据段、堆、栈)都会带来未知的结果,因为他会影响了父进程空间的数据可能会导致父进程的执行异常。

vfork()会保证子进程先运行,在他调用了exec或exit()之后父进程才可能被调度运行。如果子进程依赖于父进程的进一步动作,则会导致死锁。

使用了写时复制(CopyOnWrite)技术:这些数据区域由父子进程共享,内核将他们的访问权限改成只读,如果父进程和子进程中的任何一个试图修改这些区域的时候,内核再为修改区域的那块内存制作一个副本

2. exec*()执行另一个程序

3. wait()与waitpid()收尸函数

 4. system()与popen()函数

猜你喜欢

转载自blog.csdn.net/qq_51368339/article/details/126964687