Linux——进程概念(上)

Posted Hey pear!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux——进程概念(上)相关的知识,希望对你有一定的参考价值。

文章目录

1. 冯诺依曼体系结构

截至目前,我们所认识的计算机,都是有一个个的硬件组件组成

  • 输入单元(设备):包括键盘, 鼠标,扫描仪, 写板,磁盘,摄像头,话筒,网卡等
  • 中央处理器(CPU):含有运算器(算术运算,逻辑运算)和控制器(CPU是可以响应外部事件的,协调外部就绪事件,例如拷贝数据到内存)等
  • 输出单元(设备):显示器,打印机,音响,磁盘,网卡等
     
     

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

  • 这里的存储器指的是内存
  • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
  • 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
  • 一句话,所有设备都只能直接和内存打交道
     
  1. CPU读取数据(数据+代码)都是要从内存中读取的。站在数据的角度,我们认为CPU不和外设直接交互
  2. CPU要处理数据,需要先将外设中的数据加载到内存。站在数据的角度,外设直接和内存打交道

 
 

2. 操作系统(Operator System)

2.1 概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。

笼统的理解,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库, shell程序等等)

 

2.2 设计OS的目的

  • 与硬件交互,管理所有的软硬件资源
  • 为用户程序(应用程序)提供一个良好的执行环境定位

在整个计算机软硬件架构中,操作系统的定位是: 一款纯正的“搞管理”的软件

2.3 如何理解 “管理”

管理是对被管理对象的数据管理
管理理念:先描述,再组织
例如:先对被管理对象进行描述,再根据描述类型,定义对象,可以把对象组织成数组——>对学生的管理工作就变成了对数组的增删查改
Linux内核是用C语言写的struct
OS:内部,一定存在大量的数据结构和算法

2.4 总结

计算机管理硬件:

  1. 描述起来,用struct结构体
  2. 组织起来,用链表或其他高效的数据结构

 

2.5 系统调用和库函数概念

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

 
 

3. 进程

我们自己启动一个软件,本质其实就是启动了一个进程
在linux下,运行一条命令,其实就是在系统层面创建了一个进程
linux是可以同时加载多个程序的,linux是肯同时存在大量的进程在系统中(OS,内存)所以必须管理进程

先描述,再组织
进程 = 对应的代码和数据 + 进程对应的PCB结构体

3.1 描述进程-PCB

PCB process control block
本质是一个结构体struct
不同的操作系统中,PCB的名字不一样
Linux:struct task_struct

.exe可执行程序其实本质就是文件:代码+数据

人认识世界,是通过属性来认识的
属性也是数据
文件 = 内容+属性

PCB包含了所有进程属性(struct)
对进程的管理,变成了对进程PCB结构图链表的增删查改
PCB是新增的

3.2 task_ struct内容分类

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

3.3 组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

 

3.4 查看进程

ps
ps axj
ps axj | greap ‘myproc’
ps axj | head -1 && ps axj | greap ‘myproc’

top

ls /proc 里面的文件是动态的

cwd是当前进程的工作目录,一般都是默认打开当前路径

 
 

3.5 杀掉进程

kill -9 id

 
 

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

  • 进程id(PID)
  • 父进程id(PPID)永远都是bash
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()

        while(1)
        
                pid_t id = getpid();//获取的是自己的进程PID
                printf("hello world,pid:%d,ppid:%d\\n",id,getppid());
                sleep(1);
        


 
 

5. 通过系统调用创建进程-fork初识

5.1 pid_t fork()

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

返回值

  • 失败的时候,返回-1
  • 成功的时候:
    • 给父进程返回子进程的pid
    • 给子进程返回0

 

5.2 fork的基本用法

fork之后,代码是父子共享的

先写出程序对应的Makefile

一下代码的执行情况是打印了两次you can see me !

 

fork之后有两个不同的执行流

int main()

    pid_t id = fork();
    if(id < 0)
    
        //创建失败
        perror ("fork");
        return 1;
            
    
    else if(id == 0)
    
        //child process(task)
        while(1)
        
            printf("I am child,pid: %d,ppid: %d\\n",getpid(),getppid());
            sleep(1);      
            
    
    else
    
        //parent process
        while(1)
        
            printf("I am father,pid: %d,ppid: %d\\n",getpid(),getppid());
            sleep(1);        
            
    
    return 0;

 
以下有几个关于frok的问题:

  1. 为什么给子进程返回0,给父进程返回子进程的pid?
    比例,父进程:子进程=1:n
    所以父进程唯一,子进程可以有许多个,为了区分所以需要返回子进程的pid

  2. 为什么有两个返回值?
    因为fork内部父子会各自执行自己的return语句
    返回两次并不意味着保留两次

3 创建进程的时候,OS要做什么?
要先新建一个task_struct

  1. 当我们以及准备return了,我们的核心代码执行完了吗?
    已经完成了
    操作系统和CPU运行某一个进程,本质是从task_struct形成的队列中挑选一个task_struct,来执行它的代码
    进程调度就变成了task_struct的队列中选择一个进程的过程
    只要想到进程,优先想到进程对应的task_struct

  2. 父子进程被创建出来,哪一个会先被执行呢?
    不一定
    是由操作系统的调度器决定的

 

6. linux操作系统的进程状态

6.1 理论状态


 

  • 新建:字面意思
  • 运行:task_struct结构体在运行队列中排队,就叫做运行态
  • 阻塞:等待非CPU资源就绪,阻塞状态
    • 系统中一定是存在各种资源的(不仅仅是CPU),网卡,磁盘,显卡等等其他设备,所以系统中不只是只存在一种队列
  • 挂起:内存快不足的时候,操作系统会将长时间不执行的进程代码和数据换出到磁盘(swap)此时这个进程在内存中只有PCB,此时进程的状态就叫做挂起
  • 退出:字面意思

进程可以分为前台进程和后台进程(&
./myproc
./myproc &

 

6.2 具体状态:

  • R 运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
  • S 睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
  • D 磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  • T 停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
    • 当服务器压力过大时,os会通过一定的手段杀掉一些进程,来节省空间

 
 

6.3 进程状态查看

ps aux / ps axj 命令

 
 

7. Z(zombie)-僵尸进程

一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态
维持该状态是为了让父进程和OS来回收

僵死状态(Zombies)是一个比较特殊的状态。
当进程退出并且父进程(使用wait()系统调用)
没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

 

7.1僵尸进程危害

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。
父进程如果一直不读取,那子进程就一直处于Z状态

维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB就一直都要维护

一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。
因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
也会发生内存泄漏

创建一个维持30s的僵尸进程:

#include <stdio.h>
#include <stdlib.h>
int main()

pid_t id = fork();
if(id < 0)
perror("fork");
return 1;


else if(id > 0) //parent
printf("parent[%d] is sleeping...\\n", getpid());
sleep(30);
else
printf("child[%d] is begin Z...\\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);

return 0;

 
 

8. 孤儿进程

父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?

  • 父进程退出,子进程还在,子进程就叫做孤儿进程
  • 孤儿进程会被领养,被1号进程领养(init,系统本身)

为什么要被领养?
未来子进程退出的时候,父进程早已不在,需要领养进程来进行回收

写一个孤儿程序:

#include <stdio.h>
#include <unistd.h>

int main()

	pid_t id = fork();
	if(id == 0)
	
		//child
		while(1)
		
			printf("hello wowo!\\n");
		sleep(1);
		
	
	else
	
		//father
		int cnt = 5;
		while(cnt)
		
			printf("I am father: %d\\n",cnt--);
			sleep(1);
		
	
	return 0;

运行程序之后,屏幕上打印五行I am father: cnt–(5,4,3,2,1)
之后就一直在打印hello wowo!

这里使用ctrl+c也无法终止程序
所以需要杀掉程序的命令 kill -9 id
先使用ps命令查看ID
ps ajx I head -1
ps ajx | head -1 && ps axj | grep myproc
ps ajx | head -1 && ps axj | grep myproc | grep -v grep
while :; do ps ajx | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "**********************************"; done
如上图所示,输入命令 kill -9 69657 就可以杀掉(回收)程序了

 
 

9.进程优先级

为什么要有优先级?
本质原因就是因为CPU是有限的,而进程太多,所以需要通过某种方式竞争资源。

什么是优先级?
确认是谁应该先获得某种资源,谁后获得
(我们是可以用一些数据来表明优先级的)

调度性
优先级——调度指标

9.1 基本概念

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

9.2 查看系统进程

linux具体的优先级做法:
1优先级 = 老的优先级 + nice值
老的优先级都是80,每次设置优先级,都要从进程最开始的优先级开始设置

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

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

 

9.3 PRI and NI

PRI,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
NI,就是nice值,其表示进程可被执行的优先级的修正数值

PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice

当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行

调整进程优先级,在Linux下,就是调整进程nice值
nice其取值范围是-20至19,一共40个级别

 
 

9.4 PRI vs NI

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

9.5 查看进程优先级的命令

while :; do ps -al | head -1 && ps -la | greap myproc; sleep 1; done

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

  • 输入top

  • 进入top后按“r”
    PID to renic [default pid = 1491]

  • 输入进程PID,
    PID to renic [default pid = 1491] 17417

  • 之后回车
    Renic Pid 17417 to value

  • 输入nice值
    Renic Pid 17417 to value 20

 
 

10. 其他概念

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

CPU调度代码
时间片
抢占与出让

切换:
CPU会存在大量的寄存器
如果进程A正在运行,CPU内的寄存器里面,一定保存的是进程A的临时数据
寄存器中的临时数据叫做A的上下文
上下文的数据可以被丢弃吗?绝对不可以!!

当进程A暂时被切下来的时候,需要进程A顺便带走自己的上下文数据
带走暂时保存的目的:就是为了下次回来的时候,能恢复上去,就能按照之前的逻辑继续向后运行,就如同没有中断过一样。

cpu内的寄存器只有一份,但是上下文可以有多份,分别对应不同的进程

《Linux从0到99》五 进程概念 上

1. 冯·诺依曼体系结构

在这里插入图片描述

  • 中央处理器(cpu):进行算术运算&逻辑运算,含有运算器和控制器等。
  • 存储器:内存,用来存储数据。
  • 输入设备:常见的输入设备有鼠标,键盘,扫描仪,写板等。
  • 输出设备:常见的输出设备有显示器,打印机等。
    // 网卡由输入设备和输出设备共同组成。

注意:

  • 这里的存储器指的是内存。
  • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。
  • 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
    冯诺依曼的两个重要思想:
  • 所有的数据采用二进制存储。(契合电路的特性:高低电平)
  • 数据都保存在存储器中。(内存当中)

2. 操作系统

01 概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(Operator System,简称OS)。简单的理解,操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(如函数库, shell程序等)

在这里插入图片描述

02 设计OS的目的

  • 方便的管理电脑资源,和电脑硬件进行交互
  • 为用户程序(应用程序)提供一个良好的执行环境

03 OS的定位

在整个计算机软硬件架构中,操作系统的定位是: 一款纯正的“搞管理”的软件。

04 对OS所谓管理的理解

管理:真正的管理,具有强大的领导能力,能够具有决定权,可以区分决策和执行。
在操作系统中:
被管理者:底层硬件以及大部分软件。
管理员:操作系统。
在这里插入图片描述

  • 系统接口:操作系统为程序员提供的函数,功能比较基础,对用户的要求相对也比较高
  • 库函数:行业内大佬,将系统调用函数再次封装,提供出来的函数,称之为库函数。

3. 进程

在这里插入图片描述

01 基本概念

  • 从用户角度:进程就是一个正在运行中的程序。
  • 操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块。进程担当分配系统资源(CPU时间,内存)的实体。
    在这里插入图片描述

LINUX下进程的结构:
Linux中的进程包含3个段,分别为“数据段”、“代码段”和“堆栈段”。

  • “数据段” 存放全局变量、常数以及动态数据分配的数据空间。数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量)以及堆(存放动态分配的数据)。
  • “代码段” 存放的是程序代码的数据。
  • “堆栈段” 存放的是子程序的返回地址、子程序的参数以及程序的局部变量等。
    在这里插入图片描述

程序和进程的区别:

  • 程序本质上就是一个文件,是静态的,存储在磁盘当中。
  • 进程就是程序跑起来后生成的一个实例,是动态的,由操作系统进行管理。

02 描述进程PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
  • 课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct

1)task_struct-PCB的一种

  • 在Linux中描述进程的结构体叫做task_struct。
    t- ask_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

2)task_ struct内容分类

在这里插入图片描述

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

03 查看进程

  1. 通过proc文件系统查看
    命令ls /proc/
    在这里插入图片描述

  2. 通过ps查看
    命令ps aux
    在这里插入图片描述

可以通过ps aux | grep [名字] 查找对应的进程

04 通过系统调用获得进程标识符

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

在这里插入图片描述

05 通过系统调用创建进程-fork

fork函数:
函数形式:pid_t fork()
头文件:#include <sys/types.h>
功能: 创建一个子进程
参数: 没有参数
返回值:创建是失败返回-1,创建成功返回俩个值,子进程中返回0,父进程中返回大于0的值(子进程的PID)。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
	int ret = fork();
	printf("hello proc : %d!, ret: %d\\n", getpid(), ret);
	sleep(1);
	return 0;
}

fork 之后通常要用 if 进行分流

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

06 进程状态

下面的状态在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 */
};

1)R运行状态(running)

R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。

2)S睡眠状态(sleeping)

S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。

3)D磁盘休眠状态(Disk sleep)

D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

4)T停止状态(stopped)

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

5)X死亡状态(dead)

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

6)t跟踪状态

t跟踪状态:当进程被gdb调试的时候,会产生t状态。

7)Z僵尸状态

Z僵尸状态:子进程先于父进程退出,父进程来不及回收子进程的资源,导致子进程变成僵尸进程,处于僵尸状态。

07 进程状态查看

命令:ps aux /ps axj

  • ps aux 查看操作系统中所有的进程信息。
    在这里插入图片描述
  • ps axj 可以查看操作系统中子进程的父进程ID。
    在这里插入图片描述

08 进程状态转换

在这里插入图片描述
以上就是这篇文章的所有内容啦,感谢老铁有耐心看完。有啥错误请多多指正哈!码字不易,希望大佬们点个赞
在这里插入图片描述

以上是关于Linux——进程概念(上)的主要内容,如果未能解决你的问题,请参考以下文章

Linux 线程(进程)数限制分析

linux运维之服务进程管理

Linux系统运维1 运维 项目研发 网站 服务器 计算机基础 Linux操作系统

linux运维架构之路-MySQL多实例

[转帖]运维必读:Linux 的内存分页管理

linux云自动化运维基础知识8(进程)