Linux系统进程概念

1.冯·诺依曼体系结构

冯·诺依曼体系结构(Von Neumann Architecture),又称为普林斯顿体系结构,是一种计算机系统的基本设计范式,由数学家冯·诺依曼于1945年提出。这一体系结构成为现代计算机体系结构的基础,并在计算机发展史上具有重要意义。
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
在这里插入图片描述

输入设备:包括键盘, 鼠标,摄像头,话筒,扫描仪, 写板,磁盘,网卡等

中央处理器(CPU):含有运算器和控制器等

输出设备:显示器,打印机,音响,磁盘,网卡等

存储器:内存

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

这里的存储器指的是内存

不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)

外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。

所有设备都只能直接和内存打交道。

2.操作系统(Operator System)

概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库, shell程序等等)
设计OS的目的
与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境
在这里插入图片描述

3.进程

其实,我们自己启动一个软件,本质其实就是启动了一个进程!
在Linux在,运行一条命令,./XXX,运行的时候,其实就是在系统层面创建了一个进程

什么叫做进程:进程 = 对应的代码和数据 + 进程对应的PCB结构体

扩展:“默认打开当前路径”:每个进程都会有一个属性,来保存自己所在的工作路径

1)概念

进程是计算机中运行的程序的实例。它是操作系统进行资源分配和调度的基本单位,是计算机系统中最基本的执行单元之一。

1.程序:程序是一组指令的有序集合,用于完成特定的任务或解决特定的问题。它通常存储在磁盘等存储介质上,是静态的。
2.进程:进程是程序的一次执行过程。当程序被加载到内存中并开始执行时,就会形成一个进程。进程是动态的,具有运行状态、内存空间、寄存器集合、程序计数器等信息。
3.多进程:操作系统支持同时运行多个进程。多进程使得计算机可以同时执行多个任务,每个任务运行在独立的进程中,相互之间互不干扰。
4.进程状态:进程可以处于多种状态,如运行态、就绪态、阻塞态等。运行态表示进程正在执行,就绪态表示进程已准备好运行但还未被调度,阻塞态表示进程由于某种原因(如等待输入/输出完成)而暂时无法执行。
5.进程控制块(PCB):每个进程都有对应的数据结构,称为进程控制块。PCB包含了进程的所有信息,如进程状态、程序计数器、寄存器内容、内存分配、优先级等。操作系统通过PCB来管理和控制进程的执行。
6.进程间通信(IPC):不同的进程可能需要相互通信和共享数据。为了实现进程间的交互,操作系统提供了各种进程间通信机制,如管道、消息队列、共享内存等。
7.进程调度:多个进程争夺CPU时间执行,操作系统需要进行进程调度,决定当前应该执行哪个进程。进程调度算法的设计影响着系统的响应性能和资源利用率。

总结:
进程是程序的一次执行过程,是操作系统资源管理的基本单位。 多进程使得计算机可以同时运行多个任务。每个进程有其特有的状态、PCB和执行信息,操作系统通过进程调度来决定如何分配CPU时间和内存。进程间通信允许不同进程之间进行数据交互。进程的概念是操作系统中理解多任务处理和资源管理的重要基础。

2)PCB

PCB(Process Control Block)是一种数据结构,用于表示操作系统中的进程。每个进程都有对应的PCB,PCB包含了与该进程相关的所有信息,以便操作系统能够管理和控制进程的执行。PCB通常存储在内核的地址空间中,而不是进程的用户空间。

1.PCB中包含的信息可以因操作系统的设计而异,但通常包括以下一些关键信息:
2.进程ID(PID):唯一标识操作系统中每个进程的整数值,用于在系统中识别和管理进程。
3.进程状态:表示进程当前的执行状态,常见的状态有运行态(正在执行)、就绪态(准备执行但还未被调度)、阻塞态(等待某事件完成,如输入/输出)等。
4.寄存器集合:保存了进程在被切换出运行时的寄存器内容,包括程序计数器(PC)等。
5.进程优先级:用于调度算法中,表示进程被分配CPU时间的优先级。 内存管理信息:包括进程的内存分配情况、页表信息等。
6.文件描述符表:记录了进程打开的文件和管道等的状态和信息。 进程统计信息:如运行时间、累计CPU时间、IO操作次数等。
7.进程统计信息:如运行时间、累计CPU时间、IO操作次数等。

PCB(或task_struct)是操作系统用来管理进程的重要数据结构,它会被装载到RAM(内存)里且包含了与进程相关的所有信息,帮助操作系统管理进程的执行和资源分配。

3)查看进程

进程的信息可以通过ls /proc系统文件夹查看
要获取PID为1的进程信息,你需要查看/proc/1这个文件夹

大多数进程信息同样可以使用topps这些用户级工具来获取
top经常用来监控Linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。
但是ps命令通常我们不这么使用,首先建立下面的一段死循环代码,方便我们查看该进程

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

生成可执行程序后我们执行,同时输入命令ps axj | head -1 && ps ajx | grep 'test'
在这里插入图片描述

4)fork()

fork有两个返回值
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
一段fork()代码

#include <stdio.h>
#include <unistd.h>
int main()
{
   int ret = fork();
   int ret2=fork();
   if(ret < 0||ret2<0){
       perror("fork");
       return 1;
   }
   else if(ret == 0){ //child   
 //  while(1){
     printf("I am child : %d ,ppid:%d,ret: %d,ret2: %d\n", 
         getpid(),getppid(),ret,ret2);
       sleep(1);
   //}
   }
   else if(ret2==0){ //child   
 //  while(1){
     printf("I am child : %d ,ppid:%d,ret: %d,ret2: %d\n" ,
         getpid(),getppid(),ret,ret2);
       sleep(1);
   //}
   }
   else{ //father   
   //while(1){
     printf("I am father : %d ,ppid:%d,ret: %d,ret2: %d\n" ,
         getpid(),getppid(),ret,ret2);
       sleep(1);
   //}
   }
       sleep(1);
       return 0;
}  

现象
在这里插入图片描述
分析
在这里插入图片描述
扩展:命令
while :; do ps axj | head -1 && ps ajx | grep test | grep -v grep;sleep 1;echo "--------------";done
可以看到运行结果
kill命令
用于删除执行中的程序或工作
指令:kill [-s <信息名称或编号>][PID] 或 kill [-l <信息编号>]

4.进程状态

下面的状态在kernel源代码里定义:

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运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z僵尸状态(zombie):僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

Z僵尸状态
首先我们创建一个可以维持300秒的僵死进程的代码

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3   int main()
  4 {
  5   pid_t id = fork();
  6   if(id < 0)
  7   {
  8     perror("fork");
  9     return 1;
 10   }
 11   else if(id > 0)
 12   { //parent
 13     printf("parent[%d] is sleeping...\n", getpid());
 14     sleep(300);
 15   }
 16   else
 17   {
 18     //child
 19     sleep(20);        
 20     printf("child[%d] is begin Z...\n", getpid());
 21   }
 22    return 0;
 23

输入监控指令

ps aux | grep zombie | grep -v grep

在这里插入图片描述
僵尸进程是已经结束执行的子进程,但其进程控制块(PCB)仍保留在系统中,等待父进程调用系统调用 wait() 或 waitpid() 来获取其退出状态。僵尸进程的存在虽然不会再消耗 CPU 时间或其他资源,但它们仍然具有一定的危害和潜在问题,主要表现在以下几个方面:

1.资源浪费:僵尸进程的 PCB 仍然占用系统内核资源,尤其在大量产生僵尸进程的情况下,会浪费系统内存和进程表的空间。
2.进程数限制:系统对同时存在的进程数量有限制,如果有太多僵尸进程积压,可能会导致系统达到进程数限制,进而影响系统的正常运行。
3.父进程资源泄漏:如果父进程没有正确处理僵尸进程,不调用 wait() 或 waitpid() 来回收子进程资源,可能导致父进程的资源泄漏,尤其是文件描述符等资源没有得到释放。
4.可能导致资源耗尽:如果僵尸进程过多且父进程不进行处理,可能会耗尽进程表资源,导致其他合法进程无法创建。
解决僵尸进程问题的方法是,父进程在子进程退出后调用 wait() 或 waitpid() 等系统调用,回收子进程的资源并获取其退出状态。通过这样的处理,操作系统会将僵尸进程的 PCB 从进程表中移除,避免资源浪费和潜在问题。如果父进程不关心子进程的退出状态,也可以使用 waitpid() 函数的 WNOHANG 参数,让 waitpid() 变为非阻塞模式,即使子进程还未退出,也不会阻塞父进程的执行。

总的来说,虽然僵尸进程不会对系统造成巨大的直接危害,但它们可能会导致资源浪费、进程数限制和父进程资源泄漏等问题,因此在编写程序时,应该正确处理子进程的退出状态,避免产生过多的僵尸进程。

孤儿进程
父进程先退出,子进程后退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,要有init进程回收。

由于 init 进程会定期调用 wait() 或类似系统调用来处理已终止的子进程,所以孤儿进程的退出状态最终会被处理,其资源会被回收。因此,孤儿进程并不会像僵尸进程一样导致资源泄漏。

总而言之,孤儿进程是指在其父进程终止后,仍然在系统中运行的进程,其父进程已经不存在。操作系统会将孤儿进程的父进程 ID 设置为systemd进程的进程 ID,使systemd进程成为孤儿进程的新父进程,并最终处理孤儿进程的退出状态。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
 	pid_t id = fork();
 	if(id < 0){
		perror("fork");
 		return 1;
	}
 	else if(id == 0){//child
 		printf("I am child, pid : %d\n", getpid());
 		sleep(10);
 	}
 	else{//parent
 		printf("I am parent, pid: %d\n", getpid());
 		sleep(3);
 		exit(0);
 	}
 return 0;
}

输入监控指令

while :; do ps aux | grep Op | grep -v grep; sleep 1;echo "----------------------------"; done

在这里插入图片描述

5.进程优先级

PRI和NI
在Linux系统中,优先级由pri和nice值共同确定(优先级的数值越小,优先级越高;优先级的数值越大,优先级越低)
PRI(Priority):表示进程的静态优先级,是一个整数值,基准默认值为80,默认范围是 60 到 99(普通进程而言)。数值越小,优先级越高。负数表示高优先级,正数表示低优先级。对于普通用户创建的进程,默认的优先级为 0。如果进程的优先级值是负数,表示它是实时进程。
NI(Nice Value):表示进程的动态优先级,也是一个整数值,在-20到+19之间。数值越大,优先级越低。NI 值可以在运行时通过用户或管理员调整来影响进程的调度优先级。
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值
需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进
程的优先级变化,可以理解nice值是进程优先级的修正修正数据

其他概念

竞争性:
系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

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

并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

6.环境变量

1)基本概念

**环境变量(environment variables)**是在操作系统中用于配置程序运行环境的一种机制。它是一些由操作系统或用户定义的键值对(Key-Value pairs),其中键表示环境变量的名称,而值表示环境变量的内容。环境变量通常在操作系统的全局范围内生效,被用于存储程序运行所需的配置信息、路径、语言设置、系统资源等。

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

环境变量的优点在于它们可以用于传递配置信息给程序,而无需修改程序的源代码。这使得程序更加灵活,可以在不同的环境中运行而不需要重新编译。许多程序和操作系统本身都使用环境变量来配置其行为和设置。例如,PATH 环境变量用于指定系统的可执行程序所在的路径,使得用户可以在终端中直接运行程序而不需要指定绝对路径

2)常见环境变量及查看环境变量方法

常见的环境变量

PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash。

在我们创建一个可执行文件时,为什么还要加./才能运行,而其他指令和程序却不需要,那是因为我们所处的路径不在环境变量PATH中,当我们输入指令export PATH=$PATH:程序所在路径时,就将程序所在路径添加到了PATH中,这样就可以不带./就能直接运行了。

3)环境变量相关的命令及组织方式

命令

echo: 显示某个环境变量值

export: 设置一个新的环境变量

env: 显示所有环境变量

unset: 清除环境变量

set: 显示本地定义的shell变量和环境变量

在这里插入图片描述
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

main函数的三个参数

int main(int argc, char *argv[], char *env[])

argc(Argument Count):表示命令行参数的数量,包括程序本身。即 argc 的值至少为 1。
argv(Argument Vector):是一个指向指针的数组,每个指针指向一个表示命令行参数的字符串。
env(Environment Variable):它一个指向指针的数组,每个指针指向一个环境变量字符串。

7.进程地址空间

基于Linux kernel 2.6.32
32位平台

1)内存分布

在这里插入图片描述
通过代码了解一下内存的构造

    1 #include <stdio.h>
    2 #include <stdlib.h>
    3 
    4 int g_val=100;
    5 int g_unval;
    6 
    7 int main(int argc,char* argv[],char* env[])
    8 {
    9     printf("code addr         :%p\n",main);
   10     const char* p="hello";
   11     printf("read only         :%p\n",p);
   12     printf("global val        :%p\n",&g_val);
   13     printf("global uninit  val:%p\n",&g_unval);
   14     char* q1=(char*)malloc(10);
   15     char* q2=(char*)malloc(10);
   16     char* q3=(char*)malloc(10);
   17     char* q4=(char*)malloc(10);
   18     printf("heap addr         :%p\n",q1);
   19     printf("heap addr         :%p\n",q2);
   20     printf("heap addr         :%p\n",q3);
   21     printf("heap addr         :%p\n",q4);
   22 
   23     printf("stack addr        :%p\n",&q1);
   24     printf("stack addr        :%p\n",&q2);
   25     printf("stack addr        :%p\n",&q3);
   26     printf("stack addr        :%p\n",&q4);
   27 
   28     static int i=0;
   29     printf("static addr       :%p\n",&i);
   30     printf("args addr         :%p\n",argv[0]);
   31     printf("env addr          :%p\n",env[0]);                                                             
   32     return 0;                       
   33 } 

在这里插入图片描述

2)什么是进程地址空间

进程地址空间,即我们常说的虚拟内存

虚拟内存是计算机操作系统中的一种技术,它扩展了物理内存(RAM)的大小,使得进程能够访问比实际物理内存更大的地址空间。虚拟内存允许多个进程同时运行,每个进程都有自己独立的地址空间,从而实现了进程之间的隔离和保护。

虚拟内存的主要思想是将物理内存和磁盘空间结合起来,形成一个连续的地址空间,称为虚拟地址空间。这个虚拟地址空间对于每个进程都是独立的,每个进程都认为自己在独占使用整个地址空间,而无需关心其他进程的存在。

当进程访问虚拟地址空间时,操作系统会通过一个称为内存管理单元(MMU)的硬件部件将虚拟地址映射到物理内存或磁盘上的相应位置。如果所需的数据在物理内存中,那么访问就会立即完成。如果所需的数据不在物理内存中,操作系统会将不常用的数据从物理内存中置换(换出)到磁盘上的一个特定区域,然后将所需的数据从磁盘读取到物理内存中(换入)。这个过程是透明的,对于进程来说是不可见的。

所以在每个进程的眼里,整个内存中只有自己的存在

3)为什么要有地址空间

地址空间的存在是为了实现内存管理和进程隔离。

1.内存管理:地址空间允许操作系统有效地管理计算机的内存资源。通过地址空间,操作系统可以跟踪哪些内存地址被使用,哪些是空闲的,以及哪些是属于不同进程的。当进程需要访问内存时,操作系统通过地址映射将虚拟地址转换为物理地址,确保进程访问的内存是合法的,并且不会与其他进程的内存冲突。

2.进程隔离:地址空间实现了进程之间的隔离。每个进程都有自己独立的地址空间,使得进程之间的数据和指令相互隔离,互不干扰。这样,即使一个进程出现错误或崩溃,也不会对其他进程产生影响。进程之间的隔离还提供了更高的系统稳定性和安全性。

3.多道程序设计:地址空间使得操作系统能够在多个进程之间进行切换,实现多道程序设计。通过将进程的地址空间保存在内存中,操作系统可以暂停一个进程的执行,将其状态保存下来,然后恢复另一个进程的执行,从而实现多个进程的并发执行,提高了系统的利用率和响应性。

4.虚拟化:地址空间实现了虚拟化,使得每个进程都认为自己在独占整个内存空间。进程访问的地址是虚拟地址,而不需要关心实际物理内存的布局。这种虚拟化使得编程和应用程序开发更加简单,不需要考虑实际的物理内存位置。

总的来说,地址空间是计算机系统中内存管理和进程隔离的基础,它为操作系统提供了一种有效的内存管理机制,同时保障了不同进程之间的数据安全和隔离。地址空间的存在使得计算机系统能够同时运行多个进程,提高了系统的效率和灵活性。

4)理解进程地址空间

在这里插入图片描述
通过上面这幅图结合前面的知识我们不难理解,为什么子进程和父进程相同代码同一内存地址可以有不同的结果,亦或是之前的fork函数为什么可以有两个返回值。同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

所以在每一个进程建立时,其中内容都有其对应的虚拟地址,然后通过页表再访问内存,页表通过映射找到合适的物理地址再运行,页表存在的进程虚拟地址及对应物理地址可以做到更好的交互,比如我们使用动态内存分配函数申请的空间未使用,造成的浪费空间,页表并不会直接映射,而是有延迟分配,等待你使用后再映射,从而达到提高整机效率,让物理内存真正做到100%有效使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值