Linux进程基础

目录

操作系统(Operator System)

进程基本概念

描述进程-PCB

组织进程

查看进程

通过系统调用获取进程标示符

进程状态

Z(zombie)-僵尸进程

孤儿进程

进程优先级

切换:

环境变量


操作系统(Operator System)

概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库, shell程序等等)

设计OS的目的

与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境

定位

在整个计算机软硬件架构中,操作系统的定位是: 一款纯正的“搞管理”的软件

总结

计算机管理硬件
1. 描述起来,用struct结构体
2. 组织起来,用链表或其他高效的数据结构

系统调用和库函数概念

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

先把进程描述起来,再把进程组织起来!

进程基本概念

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

描述进程-PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
  • 课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct

task_struct-PCB的一种

  • 在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

task_ struct内容分类

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

组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

其实在我们自己启动一个软件时,本质就是启动了一个软件!                                                          在Linux下,运行一条命令,运行一个可执行文件时,都是在系统层面创建了一个进程!

Linux下是可以同时加载多个程序的,Linux也是可能同时存在大量的进程在系统中的(OS,内存)。对于这些大量的进程是如何管理的:先描述,再组织! 

磁盘中有很多可执行程序,本质也就是文件,包含了代码和数据,执行的时候将其加载到了内存,操作系统为其创建了描述该进程的内核数据结构PCB(包含了进程的所有属性),大量的进程每一个都有对应的PCB结构体描述,操作系统对于进程的管理就变成了对PCB结构体链表的增删查改。

查看进程

1.top命令:显示所有进程,相当于windows下的任务管理器


2.可以通过 ps axj | grep '进程文件名' 查看 ;ps axj | head -1 显示头部信息    

 3.通过内存级的文件系统/proc查看

 

 exe:指向可执行文件所在的路径                                                                                                 cwd:指向当前进程的工作路径                                                                                                       每一个可执行程序被运行起来成为一个进程,都会有一个属性来保存自己所在的工作路径 

通过系统调用获取进程标示符

  • 进程id(PID)
  • 父进程id(PPID

fork()之后,代码是父子进程共享的

因为fork()之后返回值id不同,父进程返回子进程的pid,子进程返回0,代码读到后面父进程能看到后面的代码,但是部分代码执行不了。这里的if和else if有没有可能是同时执行呢?:id在父进程里面是子进程的id,在子进程里面是0

两个while(1)同时执行,因为fork之后有两个不同的执行流

为什么给子进程返回0,给父进程返回子进程的pid(感性认识)

因为在fork()里面,在最后准备return的时候,子进程早已经被创建出来了,甚至子进程都可以被调度了,所以父进程直接return,子进程也会return

为什么会有两个返回值?

1.因为fork内部,父子进程会各自执行自己的return语句

2.返回两次,并不意味着会保存两次

父子进程被创建出来,哪一个进程先运行? 不一定!

原因:这个是由操作系统的调度器决定的,有可能父进程正在执行,转而cpu又去执行其他的,则父进程就会被放到运行队列的尾部,就在子进程之后了,也有可能父进程一口气把之后全部的程序执行完

进程状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在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 */
};
  • R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
     

新建状态,没有所谓的队列,实际上在linux内核中是不存在这种状态,结构拷贝完成,程序PCB结构体创建完成,资源等刚分配好,还没有到运行队列,就叫做新建状态,但是一般创建完成操作系统直接就会将其入队列了。

图一:循环里面打印,进程状态显示S                                                                                              图二:循环没有打印,进程状态显示R                                                                                              因为:cpu的运算速度非常非常快;图一没有运行状态吗?一定有,因为这部分代码已经被执行了;显示器属于外设,当进程向显示器中打印数据的时候,这个过程可能是需要一定程度的等待的,在打印的一瞬间,这个进程的PCB在阻塞队列等了一会儿,当就绪时就放到运行队列准备运行,而运行速度非常快,所以相比较而言,在阻塞队列待的时间远远大于在运行队列的时间。

运行状态:task_struct结构体在运行队列中排队

状态后面带加号(+),表示这个进程属于前台进程。

前台进程:前台任务已启动,输入命令就没有效果了,而且可以被ctrl + c终止掉。就相当于这个进程运行起来,占用了bash的对话框,命令行解释器就没法解释命令了。

执行程序时在后面加一个& 就会在后台运行,这样就不影响bash

 阻塞状态:等待非CPU资源就绪,下面程序,等待键盘输入

挂起状态:当内存不足的时候,操作系统通过适当的置换进程的代码和数据到磁盘上,此时进程的状态就叫挂起!

内存快不足的时候,操作系统会将长时间不执行进程的代码和数据换出到磁盘上

S:可中断睡眠状态(就是上面说的阻塞状态),可以给这个进程发信号,随时可以被中断唤醒

D:睡眠状态,磁盘睡眠,深度睡眠,不可以被中断,不可以被被动唤醒。(不好模拟出来,dd命令能够演示D状态)

当一个进程需要向磁盘写入数据时,这时磁盘就慢慢写入数据,而进程就是S状态;但是这时服务器压力过大的时候,操作系统就会通过一定的手段,杀掉一些进程,来起到节省空间的作用,所以看到这个S状态的程序就会杀掉它,当磁盘需要将写入数据成功与否的结果返回时,就找不到原来的进程了,为了避免这种情况,操作系统就设置了D状态,当进程需要磁盘读写数据的时候,就将进程设置为D状态,操作系统就杀不掉这个进程,只能等待其自动醒来(就是当收到磁盘读写结果的时候),否则只能关机,但是磁盘正在写入,关机都关不了,只能拔电源。

T:暂停状态,只是把代码停住了。(应用在,调试时,当运行到断点处,gdb发送kill -19号信号,暂停了进程)

X:  dead终止。当操作系统在执行其他任务,或者当前有大量的进程需要被回收,为了管理以及提高效率,就设置了X状态,表明已经准备好了,任何时候都可以被回收,并释放相关资源。一瞬间完成

Z:僵尸状态,一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态。一般是父进程或者OS来得知关心这个进程。是父进程主动检测这个进程状态。维持该状态是为了让父进程和OS来进行回收

Z(zombie)-僵尸进程

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

僵尸进程危害

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

孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init进程(系统本身)领养,当然要有init进程回收喽。

为什么要领养:未来子进程要退出的时候,父进程早已不在了,需要领养进程来进行回收。

进程优先级

基本概念

  • 什么是优先级:确认哪一个进程先获得某种资源,哪一个进程后获得。用一些数据表示
  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

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

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  • NI :代表这个进程的nice值

PRI and NI

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

PRI vs NI

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

 Linux具体的优先级做法:

优先级 = 老的优先级 + nice值。老的优先级都是80,每次设置优先级,都要从进程最开始的优先级开始设置。

为什么要有优先级:CPU是有限的,进程太多就需要通过某种方式来竞争资源

查看进程优先级的命令
用top命令更改已存在进程的nice:

  • top
  • 进入top后按“r”–>输入进程PID–>输入nice值

其他概念

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

切换:

在CPU内的寄存器是有一份的,但是上下文数据(进程在cpu中运行,寄存器中的临时数据)可以有多份,分别对应的不同的进程。如果进程正在被运行,CPU内的寄存器一定保存的是该进程的临时数据。当进程A暂时被切下来的时候,去执行进程B,需要进程A顺便带走自己的上下文数据,带走暂时保存的目的:就是为了下一次回来的时候,能恢复到原来,就能按照之前的逻辑继续向后执行,就如同没有被中断一样。进程A带走之后,进程B再将自己的上下文数据加载到CPU内从而开始执行进程B。

环境变量

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

常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash

查看环境变量方法

  • echo $NAME //NAME:你的环境变量名称

1.系统命令可以直接执行,自己写的程序必须带路径。如果不想带路径,也想和普通路径一样,可以直接运行我们的程序?

系统运行一个命令,必须先找到这个程序所在的地方,自己的程序不带路径就找不到,而系统的命令是默认可以被找到的,因为这些命令在环境变量中

测试PATH

1. 创建hello.c文件
2. 对比./hello执行和之间hello执行
3. 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
4. 将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路径
5. 对比测试
6. 还有什么方法可以不用带路径,直接就可以运行呢?

测试HOME
1. 用root和普通用户,分别执行 echo $HOME ,对比差异
. 执行 cd ~; pwd ,对应 ~ 和 HOME 的关系

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

注意:

  • 命令行上修改环境变量,只在本次对话中有效,退出登录就无效了
  • env:查看所有的环境变量
  • 登录用户不同,所匹配的环境变量是不同的

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

通过代码如何获取环境变量

  • 命令行第三个参数

main函数可以带的参数:前两个是命令行参数,最后一个是环境变量参数(每一个进程在启动的时候,启动该进程的进程传递给它的环境变量信息都可以以该参数传导进来)

命令行参数:


 命令行参数:就是自己在启动这个程序时,给这个程序传入的选项。

命令行参数的意义:可以让同样的程序,通过选项的方式选择同一个程序的不同子功能

当你在命令行上进行命令调用的时候,这个命令行参数是由你的父进程bash先拿到并且喂给你的子进程的(命令行参数也是你的父进程)

 通过系统调用获取或设置环境变量

#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}

getenv() :获取环境变量

setenv():设置环境变量

环境变量通常是具有全局属性的

  • 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}

 直接查看,发现没有结果,说明该环境变量根本不存在

  • 导出环境变量:export MYENV="hello world"
  • 再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!想想为什么?

实验

  • 如果只进行 MYENV= “helloworld” ,不调用export导出,在用我们的程序查看,会有什么结果?为什么?
  • 普通变量

注意

  • 父进程给子进程导入的环境变量;默认所有的环境变量都会被子进程继承
  • bash的环境变量从操作系统来,从操作系统的配置文件
  • 环境变量具有全局属性,可以被所有子进程继承
  • set显示所有的上下文变量
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值