进程概念

冯诺依曼体系结构

  从计算机行业发展到现在,所有的计算机都遵循着冯诺依曼体系,从未发生过改变,这足以说明其重要的地位,

概念

  我们所认识的计算机都是由一个个硬件组成的:输入设备–键盘等,输出设备–显示器等,存储器–内存条等,运算器,控制器,其中 运算器 + 控制器 就是我们常说的 CPU。
  上面所说的所有设备都是围绕着存储器工作的,因为输入输出设备的速度慢,而 CPU 处理指令的速度快,所以存储器是作为中间的数据缓冲而存在的,是至关重要的一个环节。

结构图

在这里插入图片描述

注意事项
  1. 这里的存储器指的是内存;
  2. 不考虑缓存情况,这里的 CPU 能且只能对内存进行读写,不能访问外设(输入或输出设备);
  3. 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取;
  4. 所有设备都只能直接和内存打交道;

Linux操作系统

概念
在任何一个计算机上,都包含一个基本的程序集合——操作系统,它所包含的内容有:
内核:进程管理、内存管理、文件管理、驱动管理等等;
其他程序:库函数、shell 解释器等等;
功能
  • 在整个计算机硬件架构中,操作系统是一款搞管理的软件;
  • 与硬件交互,管理所有的软硬件资源;
  • 与用户程序交互,提供良好的运行环境;
结构图

在这里插入图片描述

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

  计算机的管理方式:使用struct结构体对管理对象进行描述和具象,然后使用链表或其他数据结构将管理对象组织起来,这样就完成了基本的管理工作。

进程

CPU机制
  • 时间片:CPU 调度运行程序的时间段,由操作系统分配,在该时间段中 CPU 会对一个程序的指令进行处理,时间段一过,就会切调度下一个进程,处理下一个程序指令;

  • CPU 在处理程序指令时,会存在以下两种机制:

    • 并发:轮询处理;由于 CPU 处理指令速度非常快,所以在有多个程序处理时,就会对一个程序处理一个时间片,然后再对另一个程序处理一个时间片,循环往复,直至所有的程序指令都处理完毕;
    • 并行:同时运行;多核 CPU 同时处理多个程序的指令,没有时间片的限制;
  • 竞争性: 系统进程数目众多,而 CPU 资源只有少量,甚至 1 个,所以进程之间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,便具有了优先级;

  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰;

概念

  CPU 在处理程序时,会先从外存中将程序加载到内存中,然后再将程序加载到 CPU 的寄存器中,接着就开始处理指令,等对该程序处理完一个时间片之后,操作系统就会调度下一个程序进入寄存器中处理,但是由于寄存器空间有限,所以调度进来的程序就会覆盖上一次运行的程序,那么上一个程序运行的内容就会丢失,不过通过进程我们就可以有效解决这个问题,通过将上一次程序运行的内容信息保存下来,这样等到操作系统再次调度到该程序时,CPU 就能通过保留的信息快速恢复到上次结束的地方继续处理下去;
  对于操作系统来说,进程就是一个程序运行的描述,通过这个描述,操作系统可以进行进程调度运行管理,在 Linux 中,进程是以task_struct结构体存在的,通过task_struct结构体对程序运行进行动态描述,我们将其称之为 pcb——进程控制块;
  通过程序的运行描述,操作系统就可以调度哪个程序可以占用 CPU 去执行指令,确定要运行哪个程序时,则操作系统找到程序对应的 pcb,在 pcb 中取出程序运行的所需信息,加载到 CPU 上,CPU 就开始运行程序了;

task_struct内容
  • 进程ID-pid:描述本进程的唯一标示符,用来区别其他进程;
  • 进程状态:任务状态,退出代码,退出信号等;
  • 优先级:相对于其他进程的优先级;
  • 程序计数器:程序中即将被执行的下一条指令的地址;
  • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针;
  • 上下文数据:进程执行时处理器的寄存器中的数据;
  • I/O 状态信息:包括显示的 I/O 请求,分配给进程的 I/O 设备和被进程使用的文件列表;
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等;
  • 推荐一篇博客,详细介绍了task_struct中的内容分类:task_struct 数据结构
查看进程
  • cd /proc:跳转到进程的系统文件进行查看;
    在这里插入图片描述
  • cd /proc/ID:跳转到指定进程 ID 的文件夹下,查看该进程的详细信息;
  • top:提供了一个运行系统的动态实时视图,它可以显示系统摘要信息以及当前进程或线程的列表;
  • ps -ef/-aux:查看所有进程信息,二者显式的信息大部分相同,少部分有区别;
    在这里插入图片描述
  • ps -ef/-aux | grep 字符串:查看包含指定字符串的所有进程的详细信息;
    在这里插入图片描述
  • ps -l:查看系统进程的详细信息;
    在这里插入图片描述
  • pid_t getpid():获取当前进程 ID;
  • pid_t getppid():获取当前进程的父进程的 ID;
  • exit(0):退出当前进程;
    在这里插入图片描述
创建进程

  在 Linux 中,进程就是一个 pcb,是一个task_struct结构体,创建一个进程就是创建一个task_struct结构体;
  创建进程所用到的函数:

#include<unistd.h>
pid_t fork(void);

  该函数是一个可以创建进程的接口,通过复制调用该接口的父进程,从而创建一个子进程,子进程基本复制了父进程的大部分 pcb,包括调用fork()函数前的那部分内容;
  该函数的返回值依据进程的不同而不同,对于父进程而言,返回子进程的进程 ID,对于子进程而言,返回 0;
  由于程序计数器的存在,所以当子进程被创建之后,父子进程都会从程序的同一位置继续向下运行,但是具体那个先运行,这要看操作体统的具体进程调度,因此想要让父子进程进行不同的操作,我们可以根据pid_t fork()函数的返回值来进行分流,使其根据需要来进行程序的执行;

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main(){
  4   pid_t pid = fork();
  5   if(pid < 0){
  6     perror("fork!");
  7   }
  8   else if(pid == 0){
  9     printf("子进程运行!\n");
 10   }
 11   else{
 12     printf("父进程运行!\n");
 13   }
 14   return 0;                                                                                                                                                               
 15 }

在这里插入图片描述

进程状态
  • 运行状态( R ):进程正在运行中或者处于待运行队列中,随时可以被操作系统调度运行;
  • 可中断睡眠状态(S):进程处于等待事件完成的状态,满足唤醒条件或者休眠状态被中断则进入运行态;
  • 不可中断休眠状态(D):进程处于等待事件完成的状态,不能被中断,满足唤醒条件则可进入运行态;
  • 停止态(T):程序停止运行的状态,依然会被调度,但是什么事都不做, 可以通过发送SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行;
  • 死亡状态(X):在结束进程时一瞬间的状态,在任务列表看不到这个状态;
  • 僵尸状态(Z):进程退出了,但是资源没有被释放,处于一种等待处理的状态;
僵尸进程
  • 产生原因:通常子进程先于父进程退出后,若是父进程关注到子进程的退出原因,那么子进程就会正常结束,资源得到释放;若是父进程没有关注到子进程的退出,那么子进程退出后,子进程的 pcb 资源并不会释放,会保留下等到父进程关注,才会进行释放;
  • 危害:
    • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程一直不读取,那子进程就会一直处于 Z 状态;
    • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct中,换句话说,Z 状态一直不退出,PCB 一直都要维护;
    • 一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构对象本身就要占用内存,在 C 中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间的;
    • 用户所能创建的进程是有限的,一个僵尸进程就会浪费一个进程的创建;
  • 解决方案:
    • ①退出父进程;父进程退出后,就不需要关注子进程的 pcb 了,那么资源就会得到释放;
    • ②进程等待;这个后面会学习到;
  • 实现:
  1 #include<stdlib.h>
  2 #include<stdio.h>
  3 #include<unistd.h>
  4 int main(){
  5   pid_t pid = fork();
  6   if(pid < 0){
  7     perror("fork!");
  8     return 0;
  9   }
 10   else if(pid == 0){
 11     printf("子进程运行,休眠十秒!\n");
 12     sleep(10);
 13     exit(0);
 14   }
 15   else{
 16     printf("父进程运行,休眠二十秒!\n");
 17     sleep(20);                                                                                                                                                            
 18   }
 19   return 0;
 20 }

在这里插入图片描述

孤儿进程
  • 产生:父进程先于子进程退出,那么子进程的父进程就会改变为 1 号进程,也就是systemd进程,此时子进程就会运行在后台;
  • 实现:
  1 #include<stdlib.h>
  2 #include<stdio.h>
  3 #include<unistd.h>
  4 int main(){
  5   pid_t pid = fork();
  6   if(pid < 0){
  7     perror("fork!");
  8     return 0;
  9   }
 10   else if(pid > 0){
 11     printf("父进程运行,休眠十秒!\n");
 12     sleep(10);
 13     exit(0);
 14   }
 15   else{
 16     printf("子进程运行,休眠二十秒!\n");
 17     sleep(20);
 18   }                                                                                                                                                                       
 19   return 0;
 20 }

在这里插入图片描述

精灵进程
  • 概念:守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发送的事件。Linux上的大多数服务器就是用守护进程实现的;
  • 该进程较为复杂,我也一知半解,不敢乱讲,所以可以百度一下别人的博客;

环境变量

概念

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

组织方式

  在 Linux 中char** environ是一个全局变量,是一张环境表,它保存了所有能够访问到的环境变量的字符串的地址:
在这里插入图片描述

操作
  • 变量名=值:定义一个变量,并赋予给定的值,变量名的规范和 C/C++ 中的标识符规范一样;
  • env:查看所有环境变量信息;(可用 grep 筛选)
  • export:查看所有环境变量信息;(可用 grep 筛选)
  • set:查看所有本地定义的 shell 变量和所有环境变量的信息;(可用 grep 筛选)
  • echo $变量名:查看指定环境变量的信息;
    在这里插入图片描述
  • export 变量名:声明已有变量为环境变量或者设置一个环境变量;
  • export 变量名=变量值:设置环境变量,并设置为给定值;
  • unset 变量名:删除变量;
获取
  1. 通过环境表获得:在函数中使用extern char** environ来声明全局变量,然偶循环遍历该字符数组,获取到每一个环境变量的值;
#include <stdio.h>
int main(int argc, char *argv[]){
	//声明该数组存在,下面再使用
	extern char **environ;
	int i = 0;
	for(; environ[i]; i++){
		printf("%s\n", environ[i]);
	}
	return 0;
}
  1. main函数第三个参数:main函数是有三个参数的,第一个参数为程序运行参数个数,第二个参数为程序运行参数字符串地址,第三个参数为环境变量的字符串地址,所以当我们将main函数的第三个参数传入之后,我们就可以使用它来获取到环境参数的值;
1 #include<stdlib.h>
2 #include<stdio.h>
3 #include<unistd.h>
4 int main(int argc, char* argv[], char* environ[]){
5   for(int i = 0; environ[i] != NULL; i++){
6     printf("环境变量 %d :%s\n",i, argv[i]);//循环遍历第三个参数                                                                                                                            
7   }
8   return 0;
9 }

在这里插入图片描述

  1. 通过环境变量接口获取:该接口可以根据设置的环境变量的名字以及其他的参数来获取到环境变量的值;
    • char* getenv(const char* name);
    • int setenv(const char* name, const char* value, int overwrite);
    • int putemv(char* string);
零碎知识点
  1. main函数的前两个参数是为了确定程序的运行参数的,第一个参数为程序运行参数个数,第二个参数为程序运行参数字符串地址,通过这两个参数我么就可以在函数外面对程序进行设置,这样可以很方便的让程序具有灵活性;而函数运行参数就是在执行命令时,后面以空格隔开的那些参数选项;如下面演示的,运行一个程序指令,在后面随意跟上一些参数,然后将其打印出来:
1 #include<stdlib.h>
2 #include<stdio.h>
3 #include<unistd.h>
4 int main(int argc, char* argv[], char* environ[]){
5   for(int i = 0; i < argc; i++){
6     printf("该程序第 %d 个程序运行参数是:%s\n",i, argv[i]);
7   }                                                                                                                                                                     
8   return 0;
9 }

在这里插入图片描述
2. 环境变量通常是具有全局属性的,可以被子进程继承下去;
3. 在 shell 中运行的程序,他们的父进程都是 shell;shell 是命令解释器的统称,而我所使用的则是 bash,是其中的具体的一个,所以我所运行的程序的父进程都将会是 bash;

1 #include<stdlib.h>
2 #include<stdio.h>
3 #include<unistd.h>
4 int main(int argc, char* argv[], char* environ[]){
5   while(1){
6     sleep(1);                                                                                                                                                           
7   }                                                            
8   return 0;                                                    
9 } 

在这里插入图片描述

程序地址空间

虚拟地址

在这里插入图片描述
  相信大家一定知道上面的内存划分,而且可能认为我们所说的地址是内存的地址,是内存的物理单元编号,但是在硬件内存中并没有存在像上面的区域划分,实际上在进程中,程序访问的这些地址都是一些假地址——虚拟地址,而程序地址空间叫做进程的虚拟地址空间;
  虚拟地址空间实际上是系统给进程所描述的一个假的地址空间,是一个mm_struct结构体,系统会为每个进程描述一个假的地址空间,进程访问的都是虚拟地址,访问内存数据的时候,先将虚拟地址转换为物理地址然后进行访问;
  下面图片中的这种情况,由于程序一般都是要运行在一段连续的空间中,所以两段碎片的4M空间是无法使用的,因此系统将那些碎片化的空间使用映射表整合,然后为每个进程都描述了一个完整的、连续的、线性的虚拟地址空间,这样的话,对于每个进程都相当于是每个进程自己都有一块完整、连续的空间使用,等到程序真正的运行时,在利用映射表为其分配物理空间;
在这里插入图片描述
  虚拟地址空间是系统为每个进程,通过mm_struct结构体虚拟的一个地址空间,使用虚拟地址空间的目的就是为了让进程能够访问一块连续的、完整的空间,并且经过页表映射到物理内存之后,可以实现数据在物理内存上的离散式存储,提高内存利用率,并且在页表中可以进行内存访问控制,可以判断你是否拥有某个地址的访问权限,保证了安全性;

内存管理方式
  • 分段是内存管理:将地址空间分为多段,就像内存分区那张图片一样的划分方式,使其拥有代码段、数据段等等,这样便于编译器进行地址管理;
    在这里插入图片描述
  • 分页式内存管理:将地址空间分为多个小块,实现数据离散式存储,提高存储利用率;
    在这里插入图片描述
  • 段页式内存管理:将虚拟地址空间进行分段,在每个分段内进行分页式管理,集合了分段分页的的优点进行内存管理;
    在这里插入图片描述
交换内存

  在分页式内存管理下,页表中有一项是缺页项,这一项表示了当前地址要访问的数据不在内存中,而是暂时存放到了交换分区中,需要通过一定的手段才能拿到;
  交换分区:当物理内存不够使用的时候,则将物理内存中不活跃的数据保存到磁盘的交换分区中,将空出来的空间用来存放即将要运行的程序的数据;
  由上面的概念则引出了一个问题,什么是不活跃的数据呢?于是出现了许多的算法,用来确定不活跃的数据,例如最久未使用算法——LRU,最少未使用算法等等,对这方面感兴趣的小伙伴可以去网上搜一搜哦;

进程优先级

概念

  cpu 资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行权利,配置进程优先权对多任务环境的 linux 很有用,可以改善系统性能还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整
体性能;

PRI 与 NI
  • PRI:即进程的优先级,或者通俗点说就是程序被 CPU 执行的先后顺序,此值越小进程的优先级别越高;
  • NI:是我们所说的 nice 值,其表示进程可被执行的优先级的修正数值,加入 nice 值后,将会使得 PRI 变为:PRI(new)=PRI(old) + nice;当 nice 值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行;在 Linux 下 nice 其取值范围是 -20 至 19,一共 40 个级别;
  • 需要强调一点,NI 不是进程优先级,他只是一种会影响到进程优先级 PRI 的因素,是一个优先级修正数据;
操作
  • ps -l:可以查看到进程优先级;
    • UID : 代表执行者的身份;
    • PID : 代表这个进程的代号;
    • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号;
    • PRI :代表这个进程可被执行的优先级,其值越小越早被执行;
    • NI :代表这个进程的 nice 值;
      在这里插入图片描述
  • top:输入该命令进入后,按 “r” 后输入要修改进程的 PID,然后输入 nice 值,即可修改成功;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值