【Linux】进程的概念(1)

  1. 冯诺依曼体系结构

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

关于冯诺依曼,必须强调几点:

这里的存储器指的是内存
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
一句话, 所有设备都只能直接和内存打交道(为了提高整机效率)
程序要运行必须加载到内存,因为cpu要执行和访问我们的代码,只能对内存进行读取,因为体系结构规定。
  1. 操作系统

2.1.管理

操作系统是一个进行软硬件资源管理的软件。

为什么要管理?

通过合理的管理软件和硬件资源,为用户提供良好的/稳定的/高效的/安全的执行环境。

怎么管理?

操作系统检测各个硬件的数据通过驱动程序对硬件进行管理。
每一个硬件的数据,会被抽象为一个结构体,并且以链表的形式进行存储。
所有对硬件的管理就抽象为对结构体做管理。
操作系统的管理思路就是:先描述,再组织。
描述就是把所有的信息封装在一个结构中(面向对象的思想)
组织就是他的组织方式(数据结构)
所以管理的本质就是对数据做管理,
管理的思路就是先描述(语言)再管理(数据结构)。
操作系统对于软件也要做管理。也一样:先描述再组织。

2.2.系统调用

在开发角度, 操作系统对外会表现为一个整体,但是会暴露自己的部分接口 ,供上层开发使用,这部分由操作系统提供的接口, 叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
c语言和 c++的库就是其中一个库。
系统调用接口是c式的接口,因为操作就是C语言写的。os通过C语言给我们提供的函数调用。

为什么要以接口的形式给用户提供服务?

为了保护操作系统 ,假如任何用户,任何程序都可以访问操作系统的核心,或者修改操作系统的核心,这不都乱了嘛。所以 操作系统对外会表现为一个整体,只提供接口式服务。
  1. 进程介绍

在操作系统看来需要管理进程,也是需要先描述,再管理。

3.1.基本概念

课本概念: 程序的一个执行实例,正在执行的程序等。
内核观点: 担当分配系统资源(CPU时间,内存)的实体。

3.2.描述进程-PCB

进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息.

task_ struct内容分类 :

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息等------
  1. 查看进程

4.1.ps ajx

ps ajx :查看系统中所有的进程
先写一个程序让他运行起来成为一个进程。
#include<stdio.h>
#include<unistd.h>
int main()
 {
     printf("heello world!\n");
     int i=0;
     while(1)
     {
         ++i;                                                                            
         printf("%d我是一个进程!\n",i);
         sleep(1);
     }
     return 0;
 }
一般使用grep(行过滤工具)
ps ajx | grep“mytest"
ps ajx | grep“mytest" | grep -v grep (可以添加一个过滤条件)去掉grep那个进程。

这每一行都是什么意思?

grep ajx | head -1 :查看第一行,

怎么杀死这个程序?

kill -9 【进程id】:杀死这个id。

4.2.通过 /proc 系统文件夹

ls /proc :查看proc目录里面的我文件。
存放的是以进程pid命名的目录,只要进程在运行,就一定可以在里面找到,以进程pid命名的一个目录。
进程的信息可以通过 /proc 系统文件夹查看
是一个内存级的进程目录。
大多数进程信息使用 top和ps这些用户级工具来获取。
进程执行起来之后 ,已经被加载到了内存中 ,现在即使我们删掉,原来的exe文件,还是可以跑起来的。

  1. 系统接口:获取进程pid

进程id(PID)
父进程id(PPID)
#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
int main()    
{    
    printf("heello world!\n");    
    int i=0;    
    while(1)    
    {    
        ++i;    
        printf("%d我是一个进程!",i);    
        printf("pid:%d, ppid:%d \n",getpid(),getppid());                                    
        sleep(1);    
    }    
    return 0;    
}  
每一次重启进程,他的进程pid就会发生改变。
父进程的id一般是不会变换的。
命令行上启动的进程一般没有特殊情况,他的父进程就是bash(命令行解释器)
命令行解释器(bash)通过创建子进程的方式来执行命令 。这样子进程挂掉了不会影响父进程。
  1. 系统调用:创建进程-fork

fork():创建子进程。
fork()的返回值,很特别。
父进程返回子进程的pid;
子进程返回0;
fork();之后,会有父进程+子进程两个进程执行后续的代码。
fork后续的代码。会被父子进程共享!
通过返回值的不同,让父子进程执行后续共享的代码的一部分,这就是多进程。
  1. 进程状态

7.1.前提知识

由于操作系统的管理方式是:先描述,再组织。
每一个进程在加载进入内存的时候,操作系统会马上给进程创建进程PCB,并且完成属性记录。
操作系统会维护一个进程队列,队列中的每一个节点就是进程的PCB,方便所有进程的管理。
一个cpu一个运行队列,每一个硬件也要维护一个队列,(每一个外设的数量都有限)
让进程入运行队列,就是把进程的 PCB 入队列,而不是把进程的代码和数据入队列。
PCB里面存有,进程的代码地址,数据,进程状态----。

什么是状态?

状态是进程内部的属性,它一定在进程task_struct中存在。可能就是结构体中的一个整形变量。(1:run,2:sleep,3:dead----)
所谓进程不同的状态,本质上是进程在不同的队列中,等待某种资源。

7.1.1.运行状态和阻塞状态

我们把在CPU运行队列中等待CPU资源的进程叫做运行状态。
进程在等待某种外设资源叫做阻塞状态,在某种外设的等待队列中。

7.1.2.挂起状态

因为外设的速度很慢,假如很多进程同在等待某一个外设的时候,这个外设的队列中后面的某些进程就不会立即被调用,要等待很长时间,万一此时内存不够了怎么办,就先把某些进程的代码和数据暂时保存在磁盘上,这样就为内存节省出了一部分空间。
上述说的是阻塞挂起状态,还有一种挂起状态是运行挂起(就绪挂起),但是运行挂起的概率很小很小,除非内存是真的太小了。
挂起状态通常和别的状态一起使用,比如阻塞挂起,运行挂起,新建挂起---。总归就是为了解决内存不够的用的问题。
上面我们讲解的是所有计算机都有的状态。

7.2.Linux的具体进程状态。

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)

下面的状态在kernel源代码里定义:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

7.2.1.运行状态

R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在 运行中 要么 在运行队列 里。

7.2.2.睡眠状态

S睡眠状态(sleeping) : 意味着 进程在等待事件完成 (这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。 (睡眠状态 (S)== 阻塞状态的一种)
进程如果有访问外设,大部分都是S状态。

7.2.3.磁盘休眠状态(Disk sleep)

D磁盘休眠状态(Disk sleep): 有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
S是浅度睡眠,D是深度睡眠。
S是可以被终止的,但是D是不能被终止的(操作系统也无法杀死它),只能通过断电,进程自己执行完。
想看到D状态的进程,必须在高 io的情况下!!我怕机器挂掉,我就不演示了。
D状态是操作系统给进程发的 一个免死金牌,用于执行一些重要的数据 io,普通进程杀死就杀死了,没问题。假如一个进程正在向磁盘写数据,而且这个数据很重要。这个进程一般会进入D状态。

7.2.4.停止状态(stopped)

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
很多教材都没有暂停状态。很多教材把停止状态归结带挂起或者阻塞当中了。

kill 不止能杀死进程,还有很多指令

kill -l : 查看所有 kill指令。
kill -19 【进程pid】 :暂停进程。
kill -18 【进程pisd} : 继续运行进程。
kill -9 【进程pid】 : 杀死进程

7.2.5.跟踪停止(tracing stop)

t跟踪停止(tracing stop): 一种特殊的停止状态.
先将一个程序调试起来
查看进程状态
这个也是一种暂停状态,表示当前进程正在被追踪.
这就说明了为什么进程可以被调试.

7.2.6.死亡状态(dead)

X死亡状态(dead): 这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
我们无法看到这种状态,因为操作系统很快,对于一个已经死亡的状态,操作系统会迅速的回收它.我们无法看到.

7.2.7.僵尸进程(zombie)

僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程.
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码.
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
僵尸进程,虽然是一种正常状态.但是如果父进程一直不回收子进程的资源,这就不正常了,他就一直浪费系统资源,造成资源浪费.

僵尸进程危害

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎 么样了。可父进程如果一直不读取子进程的退出结果,那子进程就一直处于Z状态.
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话 说,Z状态一直不退出,PCB一直都要维护.
那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空 间!
这就造成了内存泄漏.

  1. 进程状态查看

ps aux / ps ajx

  1. 孤儿进程

子进程退出后,还在运行的父进程没有读取他的返回结果,子进程会变为僵尸进程.
假如子进程还在运行,父进程退出了,子进程退出后,由谁来读取它的返回结果?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收喽。
这个1号进程就是操作系统.

演示:

操作系统为什么要领养孤儿进程,如果操作系统不领养,就没有进程回收他的结果,就会出问题,内存泄漏.

  1. 进程优先级

cpu资源分配的先后顺序,就是指进程的优先权(priority)
优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能.
还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整 体性能

为什么存在进程优先级?

因为资源少,需要排队,或者确立优先级.

Linux进程优先级的特点?

优先级本质就是PCB里面的一个整数(也有可能是几个),也是属性的一种.
Linux中是由两个整数确定的.PRI和NI

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

PRI and NI

PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高
nice值(NI)其表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice 当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20至19,一共40个级别。

PRI vs NI

需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进 程的优先级变化。
可以理解nice值是进程优先级的修正修正数据

用top命令更改已存在进程的nice:

top (sudo top) 进入top后按“r”–>输入进程PID–>输入nice值
注意老的优先级是不变的 一直是80,上图中虽然PRI是79 但是我们再次把NI改为哦1的时候PRI又会变为81, 注意每一次设置NI的时候PRI都会先变为80.

其他概念

竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级 . 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进.
  1. 进程切换

进程在运行的时候会产生很多的临时数据,会存在CPU的各种寄存器中,
一个进程在运行的时候不是一直占有CPU资源,现在的CPU一般都是采用时间片轮转,每个进程都有自己的时间片.
当一个进程时间片用完,需要释放CPU资源的时候,就要保留CPU寄存器中的数据, 这叫上下文保护;
当一个进程重新分配到时间片,会把是上下文保护保留的数据重新恢复到CPU的寄存器中, 这叫上下文恢复.
进程在切换的时候,要进行上下文保护,当进程在恢复运行的时候,就要进行上下文恢复.
寄存器是每个进程共有的,但是寄存器里面的数据是每个进程各自有一份的,叫进程上下文.
  1. 环境变量

12.1.基本概念

环境变量(environment variables)一般是指在操作系统中用来 指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

12.2.常见环境变量

PATH : 指定命令的搜索路径

linux中的指令其实就是一个可执行程序,和我们写的程序编译出来的可执行程序其实一样的.
为什么我们自己写的程序需要加 ./mytest
但是一般指令系统指令不需要加 ./ 这就是因为有环境变量的存在.
首先要运行指令最重要的是要找到指令,指令发出后操作系统首先会在PATH环境变量规定的路劲下寻找,找到执行它,找不到便报错.
而我们自己写的程序没有在系统规定的路径下,也就只能添加路劲执行.
正是因为系统中有PATH这样一个环境变量,我们的指令会默认去PATH指定的路劲下去找,所有指令不需要加路劲就可以执行.

所有想要我们自己写的指令不添加路劲直接执行,有两种方法,

一是:把我们的可执行拷贝到PATH存在的路劲下
二是:修改PATH,给PATH添加一个搜索路径.

HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)

HOSTNAME :主机名称

LOGNAME:当前登录主机的用户.

PWD:当前所在路径.

HISTSIZE :记载历史命令最多个数.

SHELL : 当前Shell,它的值通常是/bin/bash

USER: 当前登录用户

12.3.查看环境变量方法

echo $NAME //NAME:你的环境变量名称
env :查看所有的环境变量

12.4.环境变量相关的命令

1. echo: 显示某个变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的shell变量和环境变量

打印的比较多,不仅打印环境变量,本地变量也会打印.

12.5.环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
每一个字符串都是一个环境变量.

12.6.代码中获取环境变量

getenv():直接获取

getenv("NAME") 函数,//NAME是环境变量的名称
还有putenv,以后讲解.
返回值是char* 类型的,可以直接打印.

char ** environ:环境变量表

这是系统给我们创建的环境变量表格,通过environ指向这个表格的起始端.供我们调用,是一个全局变量
和mian函数的第三个参数相同

  1. main函数的三个参数

13.1.argc 和argv[]:命令行参数表

这两个参数就是命令行列表,存放我们调用这个程序对应的可执行文件名称和对应的参数
给不同的参数就可以执行不同的代码 .
这就是为什么,我们的系统指令中可以给不同的选项执行不同的功能.

13.2.env:环境变量表

父进程通过env(环境变量表)将环境变量传递给子进程,并且以NULL结尾.
我们写一个程序打印一下:
执行之后可以清楚的看到env就是环境变量表,保存这从父进程/操作系统哪里继承过来的各种环境变量.

  1. 提问

猜你喜欢

转载自blog.csdn.net/zxf123567/article/details/129632345
今日推荐