当前位置:   article > 正文

linux系统编程之进程概念(操作系统---管理,进程创建,进程状态,进程优先级, 环境变量,程序地址空间,进程O(1)调度方法)_是linux用户层的工作单元,也是linux进行系统调度的单元。

是linux用户层的工作单元,也是linux进行系统调度的单元。

系统编程:

	进程概念->进程控制->基础IO->进程间通信->进程信号->多线程
  • 1

进程概念

冯诺依曼体系结构----现代计算机硬件体系结构

在这里插入图片描述
冯诺依曼体系结构----现代计算机硬件体系结构

	计算机五大硬件单元:
	输入设备:键盘
	输出设备:显示器
	存储器:内存-外存---
				固态接口类型SATA   SATA3  PCI-E(目前最好)
	运算器:CPU—主频2.5GHz,
				主频越大代表时钟震荡周期越高,代表1s中处理的指令也就越多
	控制器:CPU
	所有设备都是围绕存储器工作的
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

  • 这里的存储器指的是内存 不考虑缓存情况,
  • 这里的CPU能且只能对内存进行读写
  • 不能访问外设(输入或输出设备) 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
  • 一句话,所有设备都只能直接和内存打交道

对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊 天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发 送文件呢?

硬件决定了软件的行为

操作系统—管理

在这里插入图片描述
操作系统:

一个软件安装在计算机硬件上
  • 1

目的:

为了让计算机更加好用—功能:合理统筹管理计算机上边的软硬件资源
  • 1

管理:

先描述使用pcb描述进程,使用双向链表将pcb串起来进行管理,再组织。
  • 1

库函数与系统调用接口的关系:

封装关系:库函数封装了系统调用接口,是上下级的调用关系
  • 1

进程概念----进程是什么

进行中的程序
linux是一个多任务操作系统,表示有大量的程序需要被cpu调度运行,这时候cpu使用了分时技术,分别轮询处理每一个进程,在进程程序切换调度时,需要记录运行信息,因此操作系统在调度进程在cpu上运行时,使用pcb对运行中的程序进行描述,通过调度pcb完成对进程的调度,因此进程是pcb。
pcb对运行中程序进行描述
每一个运行的程序都是pcb

在操作系统角度,操作系统通过pcb来控制一个进程的运行,这个pcb也叫进程描述符,描述了一个运行中的程序
Linux操作系统下的PCB是task_struct结构体(用双向链表进行组织的)

什么时task_struct结构体

参考链接

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

内存指针

pcb中有一个指针指向了当前要运行的程序
cpu通过pcb内存指针知道代码在什么位置,然后加载到内存上面
  • 1
  • 2

cpu分时机制

不会体会到卡顿的原因,调度进程时,不会一直在一个进程上面运行,轮询调度pcb 。
每个都执行一段时间,切换速度很快。
每个进程只运行很短的一段时间(时间片)
  • 1
  • 2
  • 3

程序计数器

即将执行的指令的地址
  • 1

上下文数据

cpu正在处理的数据是什么
  • 1

标识符PID

每一个进程都有一个ID
  • 1

进程状态

当前进程处于什么状态
  • 1

优先级

前台进程(交互式进程)优先级更高
批处理(后台进程)
  • 1
  • 2

IO状态信息

每一个进程里面都会打开很多的文件,打开文件就要进行管理
记录描述文件,所以需要保存下来这些信息
  • 1
  • 2

记账信息

一个进程大致在cpu上运行了多长时间
  • 1

进程查看

ps
-ef 
-aux		查看系统所有进程信息
  • 1
  • 2
  • /proc 保存系统中正在运行的程序信息
  • pid_t getpid() 获取调用进程的pid
    在这里插入图片描述
  • 根目录下的proc/目录存放的就是当前操作系统上面正在运行中的程序的运行信息
例如
#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    
  while(1){    
    sleep(1);        
  }    
  return 0;       
}   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

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

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    
  printf("pid: %d\n", getpid());    
  printf("ppid: %d\n", getppid());    
  return 0;    
}   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述

进程创建

创建进程就是创建pcb
在这里插入图片描述
用fork创建进程
fork()—通过复制调用进程(父进程)创建一个新的进程(子进程)
子进程与父进程完全相同
在这里插入图片描述
在这里插入图片描述
head line 打印了一次
tail line 打印了两次
在这里插入图片描述
复制了父进程的pcb(意味着和父进程拥有一样的内存指针,程序计数器,上下文数据):
和父进程运行相同的代码,相同的运行位置,
处理一样的数据

父子进程代码共享,数据独有 。
同一个内存区域,打印的值相同

子进程创建成功都是从下一步指令开始运行

如何分辨父子进程:通过返回值
在这里插入图片描述
父子进程不一定谁先运行,要看cpu调哪个pcb
父进程:

	返回子进程的pid,pid>0
  • 1

子进程:

返回0
  • 1

失败:

返回-1
  • 1

为什么要创建子进程?意义何在?

  1. 分摊压力,cpu资源足够的情况父子进程同时处理数据,效率高
  2. 希望子进程完成其他的任务

进程状态

普遍的系统的三种状态

就绪,运行,阻塞
  • 1

在这里插入图片描述
Linux进程状态:

  1. 运行态(R)
  2. 可中断睡眠态(S)
  3. 不可中断睡眠态(D)
  4. 停止态(T)
  5. 僵死态(Z)
  6. 死亡态(X)
  7. 追踪态(t)

在这里插入图片描述
加号代表前台进程

cpu使用率非常高,什么原因?

死循环。

杀死进程

kill  进程ID
  • 1

普通杀死进程杀不死停止态进程
在这里插入图片描述
要用强杀

kill -9 进程ID
  • 1

在这里插入图片描述
僵尸进程:
处于僵死态的进程----进程退出后,资源没有完全释放(没有完全退出)
强杀都杀不死
如何产生?
在这里插入图片描述
子进程先于父进程退出,将自己退出原因保存在pcb中,操作系统检测到子进程退出,因为父进程有可能关注退出原因,所以不敢随意释放所有资源,通知父进程子进程的退出,但是这时父进程可能正在打麻将,没有关注到这个通知,导致子进程退出了
但是资源一直没有 释放,处于僵尸进程,处于僵死状态,成为僵尸进程。

危害:资源泄露,一个用户能够创建的进程是有限的,导致新进程创建失败
处理:干掉父进程
如何避免:

进程等待
  • 1

孤儿进程

父进程先于子进程退出,子进程成为孤儿进程,运行在操作系统后台,父进程成为1号进程(被领养)
  • 1

孤儿进程的使命就是不断奋斗最后成为守护进程
守护进程/精灵进程

特殊的孤儿进程     一个特殊的孤儿进程(脱离终端,脱离登会话的孤儿进程)
  • 1

进程优先级

通过一个评级来决定一个进程的cpu资源优先分配权
为了让计算机运行的更加合理
(因为进程的性质各有不同—批处理/交互式)
查看:

ps  -l
  • 1

修改:优先级无法直接修改,但是可以通过修改NI的值,来调整PRI的值
PRI=PRI+NI
renice程序运行后修改 (nice的范围(-20~19))

	Renic  -n ni_val  -p  pid
  • 1

nice程序运行时指定

	nice  -n  ni_val  ./main
  • 1

优先级调整更多的是针对cpu密集型程序(对cpu资源要求比较高)
磁盘密集型程序因为本事呢对cpu资源要求不是很高,因此大多数情况下,没必要调整

在这里插入图片描述
我们很容易注意到其中的几个重要信息,有下:

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

PRI and NI

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

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

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

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

环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 

如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。 

环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性 
  • 1
  • 2
  • 3
  • 4
  • 5

就是内存解释
和环境变量相关的命令

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

环境变量的组织方式
在这里插入图片描述
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
常见环境变量:HOME SHLL PATH

通过第三方变量environ获取
**int argc 参数个数
char argv[] 字符串指针数组放的是参数
char env[] 这个字符串指针数组所保存的就是环境变量

#include <stdio.h>
 
int main(int argc, char *argv[], char *env[])
 {    int i = 0;   
  for(; env[i]; i++)
 {        printf("%s\n", env[i]);    }    
 return 0; 
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

环境变量通常是具有全局属性的
环境变量通常具有全局属性,可以被子进程继承下去

#include <stdio.h> #include <stdlib.h>

int main() 
{  
  char * env = getenv("MYENV"); 
   if(env)
   {       
    printf("%s\n", env);  
     }    
   return 0;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

直接查看,发现没有结果,说明该环境变量根本不存在
导出环境变量 export MYENV=“hello world”
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!想想为什么?

子进程崩溃了,对shell本身没有影响
  • 1

程序地址空间

为什么要用虚拟地址空间+页表:保持进程独立性+充分利用内存+内存访问控制
段页式内存管理:段号+段内地址+页内偏移
段式内存管理:段号+段内地址
页式内存管理:页号+页内偏移
在这里插入图片描述
#include <stdio.h> #include <unistd.h> #include <stdlib.h>

int g_val = 0;

int main()
 {    pid_t id = fork();    
 if(id < 0)
 {        perror("fork");      
   return 0;   
    }
    else if(id == 0)
    { //child
         printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);   
          }
         else
         { //parent       
          printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);   
           }  
           sleep(1);  
             return 0; 
             }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里插入图片描述
我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变 量进行进行任何修改。可是将代码稍加改动:

#include <stdio.h>
 #include <unistd.h>
  #include <stdlib.h>
 
int g_val = 0;
 
int main() 
{    
pid_t id = fork(); 
   if(id < 0)
   {        
   perror("fork");      
     return 0;    
     }    else if(id == 0)
     { 
     //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取       
      g_val=100;       
       printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);   
        }
        else
        { 
        //parent       
         sleep(3);        
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);   
         }    
         sleep(1);   
          return 0; 
          }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里插入图片描述
我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
但地址值是一样的,说明,该地址绝对不是物理地址!
 在Linux地址下,这种地址叫做 虚拟地址 我们在用C/C++语言所看到的地址,全部都是虚拟地址!
 物理地址,用户一概看不到,由OS统一管理 
  • 1
  • 2
  • 3
  • 4

OS必须负责将 虚拟地址 转化成 物理地址 。
地址:内存区域的编号
-----进程的虚拟地址空间—内存描述符----mm_struct

操作系统通过mm_struct这个结构体给进程描述了一个虚拟的地址
如何描述:
mm_struct{
ulong size;
ulong code_start;
ulong code_end;
ulong data_start;
ulong data_end;
}
在这里插入图片描述
为什么要使用虚拟地址空间虚拟地址空间+页表
通过页表进行映射,页表可以进行标记,当前地址是可读还是可写
提高内存利用率
对内存访问进行控制
保证进程独立性

虚拟内存的方式
写时拷贝技术:提高子进程创建效率

父进程创建了子进程,但是并没有直接给子进程开辟内存,拷贝数据,
而是跟父进程映射到同一位置,
但是如果内存中数据发生的改变,那么对于改变的这块内存,
需要重新给子进程开辟内存,并且更新页表信息。
  • 1
  • 2
  • 3
  • 4

进程O(1)调度方法

一个CPU拥有一个runqueue

普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!
 实时优先级:0~99(不关心) 
  • 1
  • 2

活动队列
时间片还没有结束的所有进程都按照优先级放在该队列 nr_active: 总共有多少个运行状态的进程

queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下 标就是优先级!
从该结构中,选择一个最合适的进程,过程是怎么的呢?

  1. 从0下表开始遍历queue[140]
  2. 找到第一个非空队列,该队列必定为优先级最高的队列
  3. 拿到选中队列的第一个进程,开始运行,调度完成!
  4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
    bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率
    过期队列
过期队列和活动队列结构一模一样
 过期队列上放置的进程,都是时间片耗尽的进程 当活动队列上的进程都被处理完毕之后,对过期队列的
 进程进行时间片重新计算
  • 1
  • 2
  • 3

active指针和expired指针

  1. active指针永远指向活动队列

  2. expired指针永远指向过期队列

  3. 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在 的。

  4. 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活 动进程!

在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增 加,我们称之为进程调度O(1)算法

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/151111?site
推荐阅读
相关标签
  

闽ICP备14008679号