Linux进程基础
提示:文章内容较长,请参考目录阅读
这里写目录标题
- 一、进程基本概念
-
- (一)程序和进程
- (二) 创建进程
- (三)操作系统如何管理进程——PCB
-
- 1.task_struct内容
- 2.进程的抢占式执行
- (四)查看进程信息
-
- 1. ps aux
- 2.getpid
- (五)创建子进程
-
- 1.一般调用
- 2.分支语句
- 二、进程状态
-
- (一)六种状态
- (二)僵尸进程
- (三)孤儿进程
- 三、环境变量
-
- (一)基本概念
- (二)常见的环境变量
- (三)查看当前的环境变量
- (四)修改环境变量
-
- 1.命令行修改
- 2.文件中修改
- (五) 如何让自己的程序不加./直接执行
- (六) 通过代码获取环境变量
-
- 1.main函数的参数
- 2.第三方变量environ
- 3.系统调用getenv获取
- 四、程序地址空间
-
- (一)程序地址空间图
- (二)程序
- (三)页表
- 五、 进程优先级
一、进程基本概念
(一)程序和进程
- 程序:源代码经过编译产生的可执行的文件(静态)
- 进程:程序的一个执行实例,正在执行的程序(动态)
(二) 创建进程
在学习后面的内容之前,我们需要先创建一个进程,可以写一个死循环的代码来实现,如图:
这样便拥有了一个一直处于运行中的进程。
(三)操作系统如何管理进程——PCB
PCB(Process control block),程序控制块,用于存放进程信息,Linux操作系统下的PCB是:task_struct。
操作系统对进程的管理=描述(PCB)+组织方式(双向链表)
1.task_struct内容
- 提示符:提示符是描述进程的唯一标示符,用来区别其他进程,通过ps aux可查看
- 进程状态:运行、就绪、阻塞等。
运行状态——进程被CPU调度,进入运行状态。
就绪状态——进程已经准备好,分配到所需资源,只要分配到CPU就能立即运行。
阻塞状态——正在执行的进程由于某些事件(如I/O请求,申请缓存失败)而暂时无法运行。
- 优先级:相对于其他进程的优先级
- 程序计数器:程序中即将被执行的下一条指令的地址,用于保护现场和恢复现场。
- 上下文数据:保存寄存器中的数据,下一次切换到该进程执行时,可以接着上次的计算结果继续计算。
- I/O状态信息:保存进程打开的文件信息。每个进程被创建时,都会默认打开三个文件夹:标准输入、标准输出、标准错误。在"/proc"文件夹下查看以该进程号命名的文件夹,打开其中的"fd"目录,便会看到这三个文件:
2.进程的抢占式执行
- 并行:多个进程在多个CPU下,同时运行各自的代码,称为并行
- 并发:采用进程切换的方式,各自独占CPU运行各自的代码,交替运行,让多个进程都得以推进,成为并发。
在计算机系统中,进程多而CPU少是常态,所以经常会采用并发的方式,调度的时候从就绪队列调用程序,进行运行,谁准备好了就调用谁。所以进程之间会抢占CPU资源,运行自己的代码程序,这也是进程有不同状态的原因。
操作系统调度进程的算法:先来先服务、短作业优先、高优先级优先、时间片轮转。
(四)查看进程信息
1. ps aux
通过man指令查看ps结果如下:
这里a、u、x是ps的三个参数,用于查看系统当前的所有进程信息。
ps aux
查看结果:
如果要单独查看某个进程,可以使用指令:ps aux | grep 进程名,如:
ps aux | grep mytest
查询结果:
这里查询结果有两个,同多对比最后面的指令可以知道指令为"./mytest"的进程是我们要查看的。
2.getpid
getpid函数的作用时查看当进程的PID,返回值类型时pid_t,本质上也是int 型
通过man指令查看可以了解其属性:
getpid函数的使用方法:
结果如下,可以查看到当前进程的PID和它的父进程的PID:
(五)创建子进程
Linux中通过调用fork函数来创建一个子进程,通过man查看fork函数:
- fork是操作系统提供的函数,使用该函数要包含头文件"unistd.h"
- 当进程A调用fork函数成功之后,A进程创建出来一个子进程a,此时A是父进程,a是子进程
- 创建失败时,函数返回值为-1
- 创建成功时,返回两次,父子进程各一次。父进程中返回一个大于0的数,这个数就是子进程的进程号;子进程中返回0
- 父子进程代码相同,但数据时独有的,各自有各自的进程地址空间和页表,子进程会拷贝父进程的PCB
- 父子进程独立运行,互不干扰
- 子进程从fork之后开始执行
1.一般调用
打印结果如图:
可以看出,父进程先执行,之后子进程执行,父进程的调用返回值就是子进程的进程号,而子进程的返回值是0。
2.分支语句
因为父子进程中fork的返回值不同,所以我们可以根据这一特性让父子进程中执行不认同的代码段:
打印结果:
二、进程状态
(一)六种状态
ps aux指令查询结果中可以看到进程的状态:
通常进程会有下面几种状态:
- R运行状态(Running):表明程序在运行中或者在运行队列里
- S睡眠状态(Sleeping):进程在等待事件完成,也叫可中断睡眠
- D磁盘休眠状态(Disk sleep):也叫不可中断睡眠,这个状态进程会等待I/O结束
- T停止状态(stopped):只是暂时不执行,可以通过发送SIGSTOP信号给进程来停止进程,通过SIGCONT信号让信号继续运
- X死亡状态(dead):一个返回状态,无法查看到
- Z僵尸状态(Zombies):一种特殊的状态,详见下文
(二)僵尸进程
产生原因
僵尸状态是一种特殊的状态,当子进程退出,而父进程还在运行,但父进程没有读取到子进程的返回信息,子进程就会进入z状态,成为僵尸进程。
危害
由于父进程没有读取子进程的返回信息,所以用于保存进程信息的task_struct结构体一直未被释放,会造成内存泄漏
解决方案
- 杀死父进程,使用"kill [pid]"命令终止一个进程,或"kill -9 [pid]"强制终止一个进程
- 重启操作系统
- 父进程进行进程等待
前两种方法代价太大,常用第三种进程等待的方式。
僵尸进程实例
根据fork返回值的特性,让父子进程执行不同的程序:
打印输出的结果:
子进程打印完结果以后就退出了,而父进程处于死循环中,不会退出,子进程便成为了僵尸进程。
通过ps aux查看:
可以看出,此时子进程处于僵尸状态。
(三)孤儿进程
产生原因
父进程先于子进程退出,子进程退出后其退出信息没有人回收,子进程就成为了孤儿进程。最终,孤儿进程会被1号进程领养,其退出信息也由1号进程回收。
1号进程:
1号进程是操作系统启动的第一个进程,后续的进程大都由1号进程或者其子孙进程创建。
危害
由于孤儿进程会被1号进程所领养,所以没有危害。
注意:只有孤儿进程,没有孤儿状态
孤儿进程实例
要实现孤儿进程,可以让父进程先执行完毕,子进程进行死循环。
孤儿进程程序:
三、环境变量
(一)基本概念
环境变量是指在操作系统中用来指定操作系统运行环境的一些参数,可以理解为,操作系统通过环境变量找到运行时的一些资源。
形式
key(环境变量名称)=value(环境变量的值,可以有一个或多个),中间用":"隔开。
环境变量的组织方式
每个程序都有一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的字符串。
(二)常见的环境变量
-
PATH:指定命令的搜索路径
在命令行输入一个指令时,操作系统便会在PATH环境变量中的路径下按顺序查找该指令对应的操作,若查找到了,指令便能执行。 -
HOME:指定用户的主工作目录,即用户登录到Linux操作系统的家目录
-
SHELL:当前的命令行解释器,默认是"/bin/bash"
(三)查看当前的环境变量
- env 查看所有环境变量
- echo $[变量名] 查看某一个环境变量。
eg:
(四)修改环境变量
1.命令行修改
命令行中修改仅在当前终端有效,打开新的终端则失效;且其生命周期跟随当前终端,当前终端退出则失效。
修改已有的:
export 环境变量名称=新添加的环境变量内容
eg:
在已有环境变量后追加内容:
export 环境变量名称=$环境变量名称:新添加的环境变量内容
eg:
2.文件中修改
文件中修改的特点:
- 永久生效
- 修改后需要用"source 环境变量文件名称"使修改生效
环境变量对应文件
环境变量对应的文件有两类:
-
系统级文件:/etc/bashrc
-
用户级文件:~/.bashrc和~/.bash_profile
修改方法
增加新的可以在文本追加下面内容:
“export 变量名=变量路径”
如要修改旧的则直接在原有环境变量后面加":[value]"
(五) 如何让自己的程序不加./直接执行
有两种方式,第一种是将可执行程序CP到"/usr/bin"目录下,第二种是修改环境变量,将可执行程序路径添加到PATH环境变量中,"/usr/bin"是系统文件,尽量不要修改,一般采用第二种方式。
具体过程如下
-
1.在"/home/Skye/work"路径下创建一个可执行程序"my_path_test"
-
2.在".bash_profile"文件中PATH值的后面加上可执行程序的路径
-
3.直接输入可执行程序的名字便可以执行刚才的程序
(六) 通过代码获取环境变量
1.main函数的参数
main函数实际上有三个参数,其中env是存放环境变量的字符数组,在系统调用main函数是,会传递环境变量,保存在env数组中。
执行该程序后,会打印所有的环境便令,下图展示了部分内容:
在此,对main函数的两外两个参数也加以探索,通过程序打印这两个内容
结果如下:
可以看出"./env_main"这个指令本身也被算作了命令行参数。
2.第三方变量environ
environ是libc.so中定义的一个全局变量,指向环境变量表,environ没有包含在任何头文件中,所以使用时要用extern声明。
3.系统调用getenv获取
通过man手册查看getenv函数:
getenv是由C库提供的一个函数,使用时需要包含"stdlib.h"头文件,作用是查看特定环境变量。
使用方法:
打印结果:
四、程序地址空间
(一)程序地址空间图
这是之前学习过的程序地址空间图,而实际上,在C/C++中我们看到的地址都是虚拟地址,而实际的物理地址是看不到的,由操作系统进行统一管理。操作系统负责将程序中的虚拟地址转换为物理地址。之所以要使用虚拟地址是因为如果不同的进程直接访问物理地址空间,会造成访问混乱,进程并不知道哪一段内存正在被使用。
在32位操作系统中,系统为每个进程虚拟出来4G的虚拟地址空间,在程序访问内存时,都使用的是虚拟地址。
(二)程序
这个程序的执行步骤是这样的,创建出子进程以后,父进程分支继续执行,由于等待了一秒,所以子进程先执行内部打印程序,之后父进程再执行内部打印的代码。由于g_val是全局变量,所以子进程中对其值的更改仅在子进程生效。
打印结果:
可以看到,同一地址中打印取出来的数值不相同,由此证明,这里的地址并不是物理地址,而是虚拟地址。
(三)页表
- 页表映射的原理是,将虚拟地址分成很多个页每一页都有一个页号,一般页是4096字节,虚拟地址=页号+页内偏移,例如:虚拟地址为10000,页号则为1000/4096,取整数为2,页内偏移则为10000%4096,为1808
- 物理内存也被分为一个一个的块儿,每一块儿有对应的块号
如下图所示,父子进程虽然虚拟地址相同,但经过页表映射到了物理内存中不同的区域,所以才会有上面程序中的现象
五、 进程优先级
通过ps -l指令可查看进程的优先级
PRI与NICE的关系式:PRI(new)=PRI(old)+NICE
PRI表示进程的优先级,优先级数值越小优先级越高,0为优先级最高
修改进程优先级的方法:
- 先执行top指令
- 按"r"
- 输入要修改的进程PID
- 输入nice值
nice是修改的值,范围为-20至19