大家好,欢迎来到IT知识分享网。
目录
1️⃣进程的基本概念
在给进程下定义之前,我们先了解一下进程:
我们在编写完代码并运行起来时,在我们的磁盘中会形成一个可执行文件,当我们双击这个可执行文件时(程序时),这个程序会加载到内存中,而这个时候我们不能把它叫做程序了,应该叫做进程。
所以说,只要把程序(运行起来)加载到内存中,就称之为进程。
进程的概念:程序的一个执行实例,正在执行的程序等
2️⃣描述进程-PCB
PCB:进程控制块(结构体)
当一个程序加载到内存中,操作系统要为刚刚加载到内存的程序创建一个结构体(PCB),进程信息被放在这个结构体中(PCB),可以理解为PCB是进程的属性的集合。
- 在Linux操作系统下的PCB是:task_struct
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
在进程执行时,任意时间内,进程对应的PCB都要以下内容:
- 标示符:描述本进程的唯一标示符,用来区别其他进程
- 状态:任务状态
- 优先级:相对于其他进程的优先级
- 程序计数器:程序中即将被执行的下一条指令的地址
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块指针
- 上下文数据:进程执行时处理器的寄存器中的数据
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息:…
优先级的理解:
由于CPU只要一个,进程可以有多个,所以CPU的资源有限,操作系统在调度进程到CPU时会根据进程的优先级来判断。
程序计数器的理解:
CPU跑一个进程时,要执行它的代码,而代码是自上往下执行的(if、else、循环等除外),CPU先要取指令,然后分析指令,再然后执行指令。
取完一个指令后,CPU中的寄存器(EIP指令寄存器)会保存当前指令的下一条指令的地址,方便下次取下一个指令。
所谓的函数跳转、分支判断、循环等,都是修改EIP完成的。
上下文数据的理解:
CPU在跑一个进程时,没有跑完就开始切换其他进程,为了下次继续跑完这个进程,会保留这个进程的上下文数据,当这个进程回来时,会把上下文数据移动到CPU内部继续执行。
附加:每个运行的进程都要自己的时间片。
其他的后面会介绍!!
PCB通过双向链表相互连接,操作系统通过PCB,来找到进程。
通过一个例子来快速了解创建进程和删除进程:
🕳小明考到了一个二本学习,今天去学校报到,到了学校(等于程序加载到内存中),学校要通过小明拿走他的档案,并存放好(等于创建进程),当小明毕业了,档案被调出了学校(等于删除进程)。
3️⃣通过系统调用获取进程标示符
编写一段代码:
#include<stdio.h> #include<unistd.h> int main() {
while(1) {
printf("i am process...pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } return 0; }
通过ps aux | grep 文件名来找
其中getpid()是找进程的标示符,getppid()找父进程的标示符
4️⃣通过系统调用创建进程-fork(初识)
先编写一段只有一个进程的代码:
#include<stdio.h> #include<unistd.h> int main() {
while(1) {
printf("i am process...pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } return 0; }
只有一个进程在运行。
看下面代码及运行结果:
#include<stdio.h> #include<unistd.h> int main() {
pid_t id=fork();//创建子进程 while(1) {
if(id==0) {
printf("i am process...child---pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } else if(id>0) {
printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } else{
; } } return 0; }
我们可以得出:这段代码有两个进程,并且它们的关系是父子关系。
5️⃣进程的状态
操作系统存在着五种状态模型:
- 新建态:刚刚创建的进程,操作系统还没有把它加入可执行进程组中。
- 就绪态:进程已经做好准备,只有有机会就会开始执行。
- 运行态:该进程正在执行。
- 阻塞态:进程在某些事件发生前不能执行,如I/O操作完成。
- 退出态:操作系统从可执行进程组中释放出进程,或者自身停止,或者是因为某些原因被取消。
但,我们今天是来看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 */ };
R 可执行状态
通过命令
ps aux//查看进程的状态
注意:此可执行状态®并非上面的运行态。
进程中的R状态不代表正在运行,代表的可被调度,此运行相当于上面的就绪态。
S 睡眠状态
用代码创建一个睡眠状态的进程:
1)创建了一个可执行态进程
#include<stdio.h> #include<unistd.h> int main() {
while(1); return 0; }
2)创建休眠态进程
#include<stdio.h> #include<unistd.h> int main() {
while(1) sleep(10); return 0; }
S状态是浅度睡眠,随时可以被唤醒,也可以被杀掉。
D 磁盘休眠状态
可以表示为深度睡眠,该进程不会被杀掉,即使你是操作系统,除非我自动唤醒,才可以恢复。
为了防止这个情况的发生,操作系统就搞了个D状态。
这种状态(D)的进程杀不死。
T 暂停状态
X 死亡状态 & Z 僵尸状态
僵尸状态:一个处于僵尸状态的进程,会等待它的父进程或操作系统对它的信息进行读取,之后才会被释放。
其中,某人充当的角色是进程、警察和法医充当的角色的父进程或者操作系统。
通过代码来模拟僵尸状态的进程:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() {
pid_t id=fork(); int count=5; while(1) {
if(id==0) {
while(count){
printf("i am process..child---.pid:%d,ppid:%d\n,count: %d",getpid(),getppi d(),--count); sleep(1); } printf("child quit....\n"); exit(1); } else if(id>0) {
printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } return 0; }
用while :; do ps aux |head -1&&ps aux|grep a.out;echo "";sleep 1;done来监控进程的状态。
观察子进程的状态
死亡状态:进程被操作系统释放了或者自己退出了。
6️⃣僵尸进程
当一个进程变为僵尸状态的时候,该进程就变成了僵尸进程。
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
僵尸进程的危害
如何避免僵尸进程
可以用wait方法和waitpid方法避免,后面文章中讲。
6️⃣孤儿进程
在Linux中,进程的关系主要是父子关系。
为了解决这个问题,该子进程会立即被1号init进程领养。
通过代码来模拟僵尸状态的进程:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() {
pid_t id=fork(); int count=5; while(1) {
if(id==0) {
while(1){
printf("i am process..child---.pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } printf("child quit....\n"); exit(1); } else if(id>0) {
while(count) {
printf("i am process..father---pid:%d,ppid:%d\n",getpid(),getppid()); count--; sleep(1); } exit(0); } } return 0; }
用while :; do ps axj |head -1&&ps axj|grep a.out;echo "";sleep 1;done来监控进程的状态。
观察子进程的PPID
通过查看该子进程的信息,可以得知该进程被1号init进程领养。
7️⃣进程优先级
基本概念
CPU中的资源是有限的,不可能多个进程一起在CPU上运行,利用优先级把进程有效的先后排好,改善了系统的性能。
- cpu资源分配的先后顺序,就是指进程的优先权(priority)
- 优先权高的有优先执行权。
查看系统进程
用ps -l可以查看到进程的优先级
PRI:表示这个进程被执行的优先级,其值越小越早执行
NI:表示这个进程的nice值
PIR and NI
当然,nice也是有范围的,-20~19,一共40个级别。
查看进程的优先级
用top命令更改已存在的进程的nice
top命令
接着按r然后输入进程的PID输入nice值
附加其他概念
竞争性:系统进程数目多,而CPU的资源有限,所以进程之间是具有竞争属性的。为了高效完成任务,更合理的竞争相关资源,便有了优先级
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为
并发
8️⃣环境变量
基本概念
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
查看环境变量的方法
echo $name//其中name是你想查找的环境变量名。
常见的环境变量有PATH、HOME、SHELL
查看PATH环境变量的参数
PATH 环境变量
PATH:指定命令的搜索路径
PATH中的参数:
在Linux中的一些指令是通过PATH环境变量来查找到指令的路径进而执行的。
拿ls指令和自己写的代码程序来比较
创建text.c文件
#include<stdio.h> #include<unistd.h> int main() {
while(1) {
printf("hello PATH\n"); sleep(1); } return 0; }
这时因为在使用ls命令时,系统会通过PATH环境变量来查找ls,而a.out没办法通过PATH环境变量来找到,PATH环境变量中没有a.out的路径。
如何可以把自己写的文件可以像ls一样用呢?
把自己文件所在的路径添加到环境变量PATH中
通过export PATH=$PATH:a.out的所在的路径来
PATH=$PATH表示把旧的PATH的内容添加到新的PATH内,如何再添加新路径。
HOME 环境变量
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
在root用户下:
在普通用户下:
SHELL 环境变量
SHELL : 当前Shell,它的值通常是/bin/bash。
相关环境变量的相关的命令
echo命令
echo: 显示某个环境变量值
echo $环境变量名
export命令
export: 设置一个新的环境变量
export 环境变量名=$环境变量名:新环境变量
env命令
env: 显示所有环境变量
env
set命令
set: 显示本地定义的shell变量和环境变量
当我们定义一共变量时:
MAXYY=
然后通过set查看
set |grep MAXYY
这种变量称之为本地变量
在env中找不到,要把本地变量添加到环境变量中去
export MAXYY=100000
unset命令
unset: 清除环境变量
清除上面的MAXYY
通过unset MAXYY清楚MAXYY
环境变量的组织方式
通过代码如何获取环境变量?
每个程序中的main函数中都要参数,分别为int arge、char* argv[]、char* envp[]
其中arge表示argv中有效数据的个数,而argv是存放指向命令参数的指针数组,envp是存放指向环境变量的指针数组。
用代码演示获取环境变量:
#include<stdio.h> #include<unistd.h> int main(int argc ,char* argv[],char* envp[]) {
int i=0; while(envp[i]) {
printf("envp[%d]:%s\n",i,envp[i]); i++; } return 0; }
用代码演示argc、argv
#include<stdio.h> #include<unistd.h> #include<string.h> int main(int argc ,char* argv[],char* envp[]) {
if(argc>=2) {
if(strcmp(argv[1],"-a")==0) {
printf("hello bit\n"); } else if(strcmp(argv[1],"-l")==0) {
printf("hello Linux\n"); } } printf("\n"); return 0; }
ls是一个程序,-a、-l是命令行参数,其ls命令及命令行参数的地址都会存放在在argv中,而argv中的有效数据个数是argc。
结论:环境变量的一个系统级别的全局变量,bash之下所以进程都可以获取。
通过系统调用获取或设置环境变量
putenv
putenv:修改或增加环境变量,执行成功返回0,有错误或失败返回-1。
getenv
getenv:用来获取环境变量的内容,执行成功返回指向该内容的指针,找不到符合的环境变量返回NULL。
环境变量通常是具有全局属性的
环境变量通常具有全局属性,可以被子进程继承下去。
9️⃣进程地址空间
可是我们对它并不是很了解。当时我懵懂的以为这就是它们在内存中的分布情况,直到现在才知道这张空间布局图并不是它们在内存上面的分布情况,而是虚拟的。
如何理解虚拟地址
//数据化 struct destop{
unsinged long start_zhangsan; unsinged long end_zhangsan; unsinged long start_lisi; unsinged long end_lisi; }
虚拟地址就像上面的刻度一样。
所以,进程地址空间本质是一种内核的数据结构(mm_struct)。
在32位下,系统访问的内存4GB,也就是232的字节。
程序员访问的是232空间大小和物理内存本身是什么关系?
232空间大小相当于刻度尺,物理内存本身相当于桌子。
//区域划分 struct mm_struct{
unsinged int code_start; unsinged int code_end; unsinged int readonly_start; unsinged int readonly_end; unsinged int init_start; unsinged int uninit_end; unsinged int stack_start; unsinged int stack_end; //……………… }
为什么要有进程地址空间???
1、从此以后不会有任何系统级别的越界问题存在了
虚拟空间+页表:本质是保存内存。
📖总结
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119860.html





























