Linux----进程(上)

进程(上)

  • 1)冯诺依曼体系结构
  • 2)操作系统(OS)
      • 操作系统本质是`软硬件`资源`管理`的软件
  • 3)进程
    • 1. PCB
      • task_struct内容
        • 标示符
        • 上下文数据
    • 2. 初识fork(调用子进程)
      • ①创建子进程
      • ②fork返回值
      • ③创建多个子进程
    • 3. 进程状态
      • ①状态
      • ②S D T状态
      • ③僵尸状态(Z)
    • 4. 孤儿进程
    • 5. 进程优先级

1)冯诺依曼体系结构

参考:计算机组成原理


2)操作系统(OS)

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

  1. 狭义上:kernel(进程管理,内存管理,文件管理,驱动管理)
  2. 广义上:其他程序(例如函数库, shell程序等等)

OS的意义

  1. 方便用户使用
  2. 对上,为使用者提供一个良好的运行环境
  3. 对下,管理好底层的相关软硬件资源(充分高效的使用软硬件资源)

操作系统本质是软硬件资源管理的软件

理解管理:分为三层

  1. 管理者OS
  2. 执行者驱动
  3. 被管理者软硬件

例如
把OS比作公司领导,管理员工不是直接进行管理,而是通过每块的负责人去管理,
在领导这里有数据库存储员工数据,想要对特定的员工进行处理,需要抽取管理数据,
抽取管理数据的过程就是对员工的描述,领导对员工管理本质是通过数据来管理的


在Linux操作系统中进行资源管理也是一样,先描述 再管理,描述用struct结构体(Linux底层是用C语言写的),而由于管理的资源是很多的,需要某种数据结构组织起来,所以可以看成对双链表的增删查改


Linux----进程(上)_第1张图片

  1. 用户部分
    计算机体系是一个层状结构,任何访问硬件或者系统软件的行为,都必须通OS接口,贯穿OS进行访问操作
    OS不信任任何用户,任何对系统硬件或软件的访问都必须经过操作系统提供的接口(system call
  2. 系统调用和库函数
  1. 系统调用:在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用
  2. 库函数:系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发
    (分为不使用system call使用system call的比如printf
  1. 硬件部分:遵守冯诺伊曼结构

3)进程

概念 程序的一个执行实例,正在执行的程序等 也可以说是担当分配系统资源(CPU时间,内存)的实体

1. PCB

PCB概念:可以理解为进程属性的集合 ,进程信息被放在这个叫做进程控制块的数据结构中,称之为PCB(process control block)
在Linux中描述进程的结构体叫做task_struct,它是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
当我们运行起来一个程序时:
Linux----进程(上)_第2张图片

所以我们可以说
进程=你的程序+内核申请的数据结构(PCB)

task_struct内容

task_struct内容分类

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

更多参考:Linux中进程控制块PCB-------task_struct结构体结构


top命令相当于任务管理器
Linux----进程(上)_第3张图片

标示符

每一个进程都有唯一的标识符pid,和父进程ppid
用命令ps ajx可查看,同时配合管道 ps ajx | head -1 && ps ajx | grep test
在这里插入图片描述
一般情况下,每个运行的进程都是命令解释器的子进程Linux----进程(上)_第4张图片

也可以在目录下查看,如:要获取PID为33517的进程信息,你需要查看 /proc/33517 这个文件夹
Linux----进程(上)_第5张图片

上下文数据

进程在CPU上运行,会有很多寄存器上的临时数据(上下文数据)

  1. 进程放在CPU上之后,不是一直在运行直到进程运行结束。每个进程都有一个运行时间单位:时间片
  2. 一般进程让出CPU:
    a. 来个一个优先级更高的进程(OS必须支持抢占)
    b. 时间片到了
  3. 单CPU 单核:跑起来多个进程,通过进程快速切换的方式,在一段时间内,让所有的进程代码都得到推进,并发!
  4. 多CPU 多核:任何时刻,允许多个进程同时执行,并行!

理解进程切换

  1. CPU是在周而复始的取指令,分析指令,执行指令,CPU里的寄存器的内容也在不断变化(比如EIP是下一条指令的地址)而CPU的寄存器数据,称为进程的硬件上下文
  2. 当一个进程在运行中,由于某些原因(比如时间片到了),需要被暂时停止执行,让出CPU,需要进程保存(以便恢复)自己的所有临时数据(当前进程的上下文数据)
  3. 对于可能运行的进程的PCB会被一个运行队列(runqueue)链接起来,以便CPU查找或恢复

2. 初识fork(调用子进程)

①创建子进程

man一下fork
注意:pid_t一般是无符号整数,在这里是intLinux----进程(上)_第6张图片
头文件


在Linux下编译以下代码

#include 
#include 
#include 
int main()
{
      
	int ret = fork();
	while(1)
	{
      
		printf("hello proc : %d!, ret: %d\n", getpid(), ret);
		sleep(1);
	}
	return 0;
}

执行
在这里插入图片描述
可以看到第一个mytest的pid是36530,而第二个mytest的ppid也是36530,第一个mytest是第二个mytest的父进程
很明显第一个mytest的父进程是-bash


理解fork

  1. 程序员角度:
    操作系统中所有的进程具有独立性,为了不让进程间互相干扰,所以:
    父子共享用户代码(只读 不可修改),而用户数据各自私有一份
  2. 内核角度:
    进程=程序代码+内核数据结构(task_struct)
    创建子进程,通常以父进程为模板,其中子进程默认使用的是父进程的代码和数据(写时拷贝)

②fork返回值

man手册中的描述:
On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.
成功后返回子进程的pid给父进程,返回0给子进程,失败返回-1
编译下面代码

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

在这里插入图片描述
能同时进行两个死循环的根本原因不是if,else同时进入,也不是一个代码内可以同时进行多个循环,而是多执行流
return也是代码,是创建子进程成功后和父进程共享的代码
父进程子进程代码共享都要执行,所以肯定有两个返回值


返回给父进程pid:?

  1. 父亲可以有多个儿子,父进程为了更好的使用子进程,给每个子进程都会有标识,也就是pid (给用户用的,方便杀掉此进程)
  2. 儿子只能对应一个父亲,也就是0

对于fork返回值为不同的问题,我们后面地址空间再探讨


③创建多个子进程

数组存储,while创建


3. 进程状态

①状态

一个进程可以有多个状态(在Linux内核里,进程有时候也被叫做任务)
一般一个进程是前台进程状态后面会有一个+号比如R+,S+
在Linux源码中有这么一个数组(task_state_array):

/*
* 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) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里,可以被调度
  2. S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
    在这里插入图片描述
    由于每一次进入循环都会休眠1秒,相对于cpu执行的速度,这个时间很长了,相当于大部分时间都在休眠,虽然一直在打印
  3. D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束
  4. T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行
  5. X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

②S D T状态

S和D状态

  1. S浅度休眠:对外部事件可以做出反应
  2. D深度休眠:
    例子 一个进程在向磁盘写入的时候,由于写入磁盘需要时间,这个进程就会在内存里等待也就是休眠,
    但如果OS强制把这个进程给杀掉,磁盘写失败后,通知此进程,而此进程已经被杀掉

    所以深度睡眠状态的进程不可以被杀掉,只能等待此进程自动醒来
    D状态不能被kill

使用命令kill -l:
Linux----进程(上)_第7张图片
kill -19 pid 进入T状态kill -18 pid 恢复

③僵尸状态(Z)

理解

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

僵尸进程危害

  1. 进程的退出状态必须被维持下去,因为他要告诉父进程完成的任务情况,但父进程如果一直不读取,那子进程就一直处于Z状态
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中(Z状态一直不退出, PCB一直都要维护)
  3. 所以一个父进程创建了很多子进程,但是不回收,就会造成内存资源的浪费吗,造成内存泄漏

僵尸状态不能被kill

4. 孤儿进程

父进程先退出,子进程就称之为“孤儿进程”
此时孤儿进程的父进程是pid为1的进程system/d也就是OS

#include 
#include 
#include 
#include 
int main()
{
      
	int ret=fork();
	if(ret>0)
	{
      
       //Father 
       sleep(5);  
       printf("I am Father,now I quit\n");
       exit(0);
	}
	while(1)
	{
      
      	printf("I am son,my pid is:%d,my ppid id:%d\n",getpid(),getppid());
       sleep(1);
	}

}

在这里插入图片描述
这里是引用
一旦进程变为孤儿进程,就会由前台变为后台状态为S+


5. 进程优先级

优先级VS权限

  1. 优先级:一定能得到某种资源

  2. 权限:决定能否得到某种资源


使用命令ps -al查看优先级及各种信息
在这里插入图片描述
Linux的优先级由pri和ni共同决定
优先级的数值越小,优先级越高,不能一味的低,OS调度器要适度考虑公平问题,避免饥饿问题

  1. PRI(PRIORITY) 即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  2. NI(NICE) 表示进程可被执行的优先级的修正数值(nice其取值范围是-20至19,一共40个级别)

注意
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值

你可能感兴趣的