linux系统编程之进程概念(操作系统---管理,进程创建,进程状态,进程优先级, 环境变量,程序地址空间,进程O(1)调度方法)

系统编程:

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

进程概念

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

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

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

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

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

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

硬件决定了软件的行为

操作系统—管理

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

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

目的:

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

管理:

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

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

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

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

进行中的程序
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内存指针知道代码在什么位置,然后加载到内存上面

cpu分时机制

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

程序计数器

即将执行的指令的地址

上下文数据

cpu正在处理的数据是什么

标识符PID

每一个进程都有一个ID

进程状态

当前进程处于什么状态

优先级

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

IO状态信息

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

记账信息

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

进程查看

ps

-ef 
-aux		查看系统所有进程信息
  • /proc 保存系统中正在运行的程序信息
  • pid_t getpid() 获取调用进程的pid
    在这里插入图片描述
  • 根目录下的proc/目录存放的就是当前操作系统上面正在运行中的程序的运行信息

例如

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    while(1){    sleep(1);        }    return 0;       
}   

在这里插入图片描述

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

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
int main()    
{    printf("pid: %d\n", getpid());    printf("ppid: %d\n", getppid());    return 0;    
}   

在这里插入图片描述

进程创建

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

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

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

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

	返回子进程的pid,pid>0

子进程:

返回0

失败:

返回-1

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

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

进程状态

普遍的系统的三种状态

就绪,运行,阻塞

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

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

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

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

死循环。

杀死进程

kill  进程ID

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

kill -9 进程ID

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

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

进程等待

孤儿进程

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

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

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

进程优先级

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

ps  -l

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

	Renic  -n ni_val  -p  pid

nice程序运行时指定

	nice  -n  ni_val  ./main

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

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

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

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

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

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

环境变量

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

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

  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; }

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

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

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

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

int main() 
{  char * env = getenv("MYENV"); if(env){       printf("%s\n", env);  }    return 0;}

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

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

程序地址空间

为什么要用虚拟地址空间+页表:保持进程独立性+充分利用内存+内存访问控制
段页式内存管理:段号+段内地址+页内偏移
段式内存管理:段号+段内地址
页式内存管理:页号+页内偏移
在这里插入图片描述
#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){ //childprintf("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; }

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

#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; }

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

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

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

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

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

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

进程O(1)调度方法

一个CPU拥有一个runqueue

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

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

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

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

active指针和expired指针

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Make Menuconfig详解 (配置内核选择)

Make Menuconfig简介 make menuconfig 图形化的内核配置make mrproper -----删除不必要的文件和目录. #make config&#xff08;基于文本的最为传统的配置界面&#xff0c;不推荐使用&#xff09; #make menuconfig&#xff08;基于文本选单的配置界面&#xff0c;字符终端下…

Linux系统编程之进程控制(进程创建,fork函数,进程中止,进程等待,程序替换)

进程创建 fork()------复制&#xff0c;返回值&#xff0c;写时复制 vfork()创建子进程—子进程与父进程共用同一块虚拟地址空间&#xff0c; 为了防止调用栈混乱&#xff0c;因此阻塞父进程直到子进程调用exit&#xff08;&#xff09;退出或者进行程序替换 vfork创建的子…

Linux内核配置系统浅析

随着 Linux 操作系统的广泛应用&#xff0c;特别是 Linux 在嵌入式领域的发展&#xff0c;越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码&#xff0c;开发者在完成自己的内核代码后&#xff0c;都将面临着同样的问题&#xff0c;即如何将源代…

Linux系统编程下做一个简易的shell

自主实现一个shell--------minshell shell&#xff1a;命令行解释器-------解释执行用户的输入&#xff08;完成相对应的功能&#xff09; 步骤 1. 获取标准输入中的字符串 2. 对字符串进行解析[ls -l -a][ls ] [-l ] [-a] 3. 创建子进程 4. 子进程中进行程序替换 5. 父进程…

C++起始(内联函数,宏的优缺点,const关键字,auto关键字(C++11)基于范围的for循环(C++11). 指针空值nullptr(C++11))

内联函数 概念 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函数压栈的开销&#xff0c; 内联函数提升程序运行的效率 函数前增加inline关键字将其改成内联函数&#xff0c;在编译期间编译器会用函数体替换函数的调用…

linux内核中的汇编语言

在Linux内核代码中&#xff0c;有一部分是用汇编语言编写的。其大部分是关于中断与异常处理的底层程序&#xff0c;还有就是与初始化有关的程序&#xff0c;以及一些核心代码中调用的公用子程序。 用汇编语言编写内核代码中的部分代码&#xff0c;大体上是出于如下几个方面考虑…

数据结构课程设计---c语言实现通讯录(动态扩容+文件存储)

1 题目一 &#xff1a; 通讯录 1.1问题描述 编写一个通讯录管理系统&#xff0c;以把所学数据结构知识应用到实际软件开发中去。每条信息至包含 &#xff1a;姓名&#xff08;NAME &#xff09;街道&#xff08;STREET&#xff09;城市&#xff08;CITY&#xff09;邮编&#…

linux内核panic

1. Linux Kernel Panic的产生的原因 panic是英文中是惊慌的意思&#xff0c;Linux Kernel panic正如其名&#xff0c;linux kernel不知道如何走了&#xff0c;它会尽可能把它此时能获取的全部信息都打印出来。 有两种主要类型kernel panic&#xff0c;后面会对这两类panic做详细…

数据结构课程设计------c实现散列表(二次探测再哈希)电话簿(文件存储)

题目二 &#xff1a;散列表的设计与实现 2.1问题描述 设计散列表实现电话号码查找系统&#xff0c;使得平均查找长度不超过2基本要求 &#xff08;1&#xff09;设每个记录有下列数据项&#xff1a;电话号码、用户名、地址&#xff1b; &#xff08;2&#xff09;从键盘输入各…

科技论文----论搜索引擎现状及发展趋势

搜索引擎现状及发展趋势 【摘要】 随着最近10年中国互联网的快速发展菜互联网已经彻底改变了人们的生活方式&#xff0c;而在互联网的发展过程中。搜索引擎发挥了巨大的推动作用。本文对搜索引擎的发展历史采用的技术&#xff0c;发展现状出现的问题以及未来发展方向进行了综述…

inittab文件格式

/etc/inittab文件是Linux系统第一个进程init的配置文件。其每个记录占一行&#xff0c;每行最多512个字符。该文件的每个记录的格式为&#xff1a; id:runlevel:action:process 其中&#xff0c;id是一个不超过4个字符的标识&#xff0c;用来唯一标识一条记录。runlevel表明该条…

数据结构课程设计------扫雷游戏(升级版,可展开)

本程序由团队中的一个人所写&#xff0c;本人看懂并写下此文章 题目&#xff1a;扫雷 3.1问题描述 扫雷游戏 [基本要求] &#xff08;1&#xff09;完成棋盘的初始化并在标准显示器中显示 &#xff08;2&#xff09;通过输入行列值确定用户输入 &#xff08;3&#xff09;游…

C语言的编译链接过程的介绍

发布时间: 2012-11-08 10:17 作者: 未知 来源: 51Testing软件测试网采编 字体: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 | 推荐标签&#xff1a; DotNet 软件开发 | 感言十年 C语言的编译链接过程要把我们编写的一个c程序&#xff08;源代码&#x…

vs2013链接Mysql时出现 (由于找不到libmysql.dll,无法继续执行代码。重新安装程序可能会解决此问题)

将MySQL安装目录下的lib文件夹中 的libmysql.dll文件拷贝到C:\Windows\System32目录下即可

gcc 优化选项 -O1 -O2 -O3 -Os 优先级,-fomit-frame-pointer

少优化->多优化&#xff1a; O0 -->> O1 -->> O2 -->> O3 -O0表示没有优化,-O1为缺省值&#xff0c;-O3优化级别最高 英文解析&#xff1a; -O -O1 Optimize. Optimizing compilation takes somewhat more time, an…

const 和 #define 区别总结

const有类型&#xff0c;可进行编译器安全检查&#xff0c;#define 无类型&#xff0c;不可进行类型检查const 有作用域&#xff0c;而#define 不重视作用域&#xff0c;默认定义在指定作用域下有效的常量&#xff0c;那么#define 就不能用&#xff08;可以用#undef结束宏定义生…

Eclipse : Unresolved inclusion

Eclipse 中新建C 或C 到项目时&#xff0c;头文件报警&#xff0c;显示“Unresolved inclusion:<stdio.h>” 虽然不影响项目到编译和运行&#xff0c;确也无法查看头文件&#xff0c;让人感觉实在不爽。下面是在国外到网站上看到解决方案&#xff0c;自己整理了一下拿来分…

c++对const增强 和cosnt分配内存情况

const增强 c语言中const是伪常量&#xff0c;可以通过指针修改 c中const会放到符号表中 c语言中const默认是外部连接&#xff0c;c中const默认是内部链接 #include<iostream> using namespace std;const int m_a 10; //在全局区域里&#xff0c;受到保护&…

Linux下crontab命令的用法

任务调度的crond常驻命令 crond 是linux用来定期执行程序的命令。当安装完成操作系统之后&#xff0c;默认便会启动此任务调度命令。crond命令每分锺会定期检查是否有要执行的工作&#xff0c;如果有要执行的工作便会自动执行该工作。而linux任务调度的工作主要分为以下两类&am…

c++中引用的作用

引用的基本语法 用途起别名 Type &别名原名 引用必须初始化 一旦初始化后&#xff0c;不能修改 对数组建立引用 #include<iostream>using namespace std;//1.引用基本语法 Type &别名原名void test01(){int a 10;int &b a;cout << "a"…