目录

★ Linux ★ 进程(上)

Ciallo~(∠・ω< )⌒☆ ~ 今天,我将和大家一起学习 linux 进程~

​❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️

澄岚主页: 椎名澄嵐-CSDN博客

Linux专栏: https://blog.csdn.net/2302_80328146/category_12815302.html?spm=1001.2014.3001.5482

​❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️


目录

[壹 基本概念与基本操作](#壹 基本概念与基本操作)

[1.1 task_ struct](#1.1 task_ struct)

[1.2 查看进程](#1.2 查看进程)

[1.3 创建进程](#1.3 创建进程)

[贰 进程状态](#贰 进程状态)

[2.1 进程状态的概念](#2.1 进程状态的概念)

[2.2 运行 阻塞 挂起](#2.2 运行 阻塞 挂起)

[2.3 进程状态查看](#2.3 进程状态查看)

[2.4 僵尸进程](#2.4 僵尸进程)

[2.5 孤儿进程](#2.5 孤儿进程)

[叁 进程优先级](#叁 进程优先级)

[3.1 基本概念](#3.1 基本概念)

[3.2 查看系统进程](#3.2 查看系统进程)

[3.3 PRI and NI](#3.3 PRI and NI)

[3.4 查看进程优先级的命令](#3.4 查看进程优先级的命令)

[3.5 竞争、独立、并行、并发](#3.5 竞争、独立、并行、并发)

[肆 进程切换](#肆 进程切换)

[伍 Linux2.6内核进程O(1)调度队列](#伍 Linux2.6内核进程O(1)调度队列)

[5.1 优先级](#5.1 优先级)

[5.2 活动队列](#5.2 活动队列)

[5.3 过期队列](#5.3 过期队列)

[5.4 active指针和expired指针](#5.4 active指针和expired指针)

~完~


壹 基本概念与基本操作

进程是程序的一个执行实例,正在执行的程序等,或者说担当分配系统资源(CPU时间,内存)的实体。

进程信息(PCB) 被放在一个叫做进程控制块的数据结构中,Linux操作系统下的PCB是: task_struct。进程的所有属性都可以直接或间接的通过task_struct找到。

进程 = PCB(struct_task)+ 自己的代码和数据 ~

对进程的管理就变成了对链表的增删查改 ~

1.1 task_ struct

task_ struct 中包含:

标示符 : 描述本进程的唯一标示符,用来区别其他进程。

状态 : 任务状态,退出代码,退出信号等。

优先级 : 相对于其他进程的优先级。

程序计数器 : 程序中即将被执行的下一条指令的地址。

内存指针 : 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据 : 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

I/O状态信息 : 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息 : 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息

1.2 查看进程

历史上我们执行的所有指令,工具,自己的程序,运行起来都是进程~

我们可以通过系统调用getpid 获取进程标示符~

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    while(1)
    {
        sleep(1);
        printf("我是一个进程~,我的pid: %d\n", getpid());
    }
}   

每次执行的进程值都不同~

终止进程可以使用 ctrl + C 或 kill -9~

进程的信息可以通过/proc 系统文件夹查看 ~

进程结束后此进程的pid为名的文件夹会被销毁 ~

因为cwd默认当前目录,所以fopen创建文件时会在当前目录下生成~

更改目录可以使用 chdir (也是系统调用)~

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 int main()
  5 {
  6     chdir("/home/zmzz");
  7     fopen("Ciallo.txt","a");
  8     while(1)
  9     {
 10         sleep(1);
 11         printf("我是一个进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
 12     }
 13 }   

getppid() 可以查看父进程,父进程不会变(bash命令行解释器本质是一个进程~)

每有一个用户就会有一个bash

1.3 创建进程

fork可以用来创建子进程~

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 int main()
  5 {
  6     printf("父进程开始运行:pid= %d\n", getpid());
  7     fork();
  8     printf("进程开始运行:pid= %d\n", getpid()); 
  9 }      

fork有两个返回值~

一个父进程可以有多个子进程,是1:n的关系,父进程也需要知道子进程的pid进行控制,所以fork会给父子不同的返回值~

在fork函数中return id;语句执行前子进程已经被创建和调度了~所以fork会返回两次~

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    printf("父进程开始运行:pid= %d\n", getpid());
    pid_t id  = fork();
    if (id < 0)
    {
        perror("fork fail~");
        return 1;
    }
    else if (id == 0)
    {
        // child
        while(1)
        {
             sleep(1);
             printf("我是一个子进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
        }
    }
    else
    {
        // father
        while(1)
        {
             sleep(1);
             printf("我是一个父进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
        }
    }
    printf("进程开始运行:pid= %d\n", getpid());
}

进程具有独立性,父子任何一方进行修改数据,OS会把修改的数据在底层拷贝一份,让目标进程修改这个拷贝~这个拷贝叫做采用写时拷贝~

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int gval = 100;

int main()
{
    printf("父进程开始运行:pid= %d\n", getpid());
    pid_t id  = fork();
    if (id < 0)
    {
        perror("fork fail~");
        return 1;
    }
    else if (id == 0)
    {
        printf("我是一个子进程~,我的pid: %d,我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);
        sleep(5);
        // child
        while(1)
        {
             sleep(1);
             printf("子进程修改变量:%d->%d\n", gval, gval+10);
             gval += 10; // 修改
             printf("我是一个子进程~,我的pid: %d,我的父进程id: %d\n", getpid(), getppid());
        }
    }
    else
    {
        // father
        while(1)
        {
             sleep(1);
             printf("我是一个父进程~,我的pid: %d,我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);
        }
    }
    printf("进程开始运行:pid= %d\n", getpid());
}

贰 进程状态

2.1 进程状态的概念

进程状态本质上就是task_struct内的一个整数。不同的整数代表不同的状态。

2.2 运行 阻塞 挂起

**运行:**进程在调度队列中,进程的状态都是running。

**阻塞:**等待某种设备或资源就绪。(键盘,显示器,网卡,磁盘,摄像头,话筒等)

cpp 复制代码
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 信号让进程继续运行。
  • t停止状态(tracing stopped): 被debug,断点,进程被暂停了。
  • X死亡状态(dead): 这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.3 进程状态查看

cpp 复制代码
ps aux / ps axj 命令
  • a:显示一个终端所有的进程,包括其他用户的进程。
  • x:显示没有控制终端的进程,例如后台运行的守护进程。
  • j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
  • u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等

2.4 僵尸进程

僵死状态(Zombies)是一个比较特殊的状态。

创建子进程的目的是为了帮父进程完成某种事情的,父进程需要知道子进程结果相关的信息。此信息会被存在task_struct中。如果父进程一直不回收子进程的退出信息,那么Z状态的PCB会一直存在,会引起内存泄漏问题。

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int count = 5;
        while(count)
        {
            printf("我是子进程,我正在运行:%d\n", count);
            sleep(1);
            count--;
        }
    }
    else
    {
        //parent
        while(1)
        {
            printf("我是父进程,正在运行...\n");
            sleep(1);
        }
    }
    return 0;
}


// while :; do ps ajx | head -1; ps ajx | grep myprocess; sleep 1; done

2.5 孤儿进程

父进程先退出,子进程被1号systemd(操作系统)进程领养 ,这个子进程就称之为"孤儿进程"。

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        while(1)
        {
            printf("我是一个子进程, pid = %d, ppid = %d\n", getpid(), getppid());
            sleep(1);
        }
    }
    else
    {
        // father
        int cnt = 5;
        while(cnt)
        {
            printf("我是一个父进程, pid = %d, ppid = %d\n", getpid(), getppid());
            cnt--;
            sleep(1);
        }
    }
    return 0;
}

// while :; do ps ajx | head -1 && ps ajx | grep myprocess;sleep 1;done

1号进程:

变成孤儿进程后,此进程会成为后台进程,ctrlC不能终止,需使用:

cpp 复制代码
kill -9 此进程pid

叁 进程优先级

3.1 基本概念

进程优先级就是进程得到CPU资源的先后顺序~

CPU资源稀缺,所以导致要通过优先级确认谁先谁后的问题~

优先级 是能得到资源,谁先谁后的问题。而权限是能不能得到资源的问题~

优先级也是一种数字, int, 值越低,优先级越高,反正,优先级越低~ 基于时间片的分时操作系统,考虑公平性,所以优先级可能变化,但变化幅度不会太大~

3.2 查看系统进程

cpp 复制代码
ps -al | head -1 && ps -al | grep myprocess

• UID : 代表执行者的身份

• PID : 代表这个进程的代号

• PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

• PRI :代表这个进程可被执行的优先级,其值越小越早被执行

• NI :代表这个进程的NICE值

top r 10命令后:

3.3 PRI and NI

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

所以,调整进程优先级,在Linux下,就是调整进程nice值,PRI=80+NICE值

Linux优先级范围为**[60, 99]**,nice其取值范围是-20至19,一共40个级别。

3.4 查看进程优先级的命令

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

  • top
  • 进入top后按"r"-->输入进程PID-->输入nice值

3.5 竞争、独立、并行、并发

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

肆 进程切换

比如一个大学生要去当兵,去之前他得向辅导员申请保留学籍,一年之后回来时他又得向辅导员申请恢复学籍,这就相当于一次切换,学校就是CPU,导员是调度器,大学生就是进程,学籍是进程运行的临时数据(CPU内寄存器中的内容,当前进程的上下文数据),保留学籍恢复学籍分别相当于保存和恢复进程上下文数据,当兵就是从CPU上剥离下来了。

当前进程要将自己的进程硬件上下文保存到进程的task_struct中(TSS任务状态段)。


伍 Linux2.6内核进程O(1)调度队列

下图是Linux2.6内核中进程队列的数据结构~

一个CPU拥有一个runqueue, 如果有多个CPU就要考虑进程个数的负载均衡问题~

5.1 优先级

  • 普通优先级:100~139(x - 60 + (140 - 40))
  • 实时优先级:0~99(不关心)

5.2 活动队列

  • 时间片还没有结束的所有进程都按照优先级放在该队列
  • nr_active: 总共有多少个运行状态的进程
  • queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO 规则进行排队调度,所以,数组下标就是优先级!相当于一个哈希表管理的一个个链式队列
  • 从该结构中,选择一个最合适的进程,过程是怎么的呢?
  1. 从0下表开始遍历queue[140]

  2. 找到第一个非空队列,该队列必定为优先级最高的队列

  3. 拿到选中队列的第一个进程,开始运行,调度完成!

  4. 遍历queue[140]时间复杂度是常数!但还是太低效了!

  • bitmap[5] :一共140个优先级,一共140个进程队列 ,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!

5.3 过期队列

过期队列和活动队列结构一模一样,过期队列上放置的进程,都是时间片耗尽的进程,当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。

5.4 active指针和expired指针

  • active指针永远指向活动队列
  • expired指针永远指向过期队列

活动队列上的进程会越来越少,过期队列上的进程会越来越多 ,在合适的时候,只要交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!


~完~

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
唐青枫18 分钟前
Linux使用pidof命令来快速查找进程id
linux
CharlesG01 小时前
NAT 实验:多私网环境下 NAPT、Easy IP 配置及 FTP 服务公网映射
运维·服务器·网络·网络协议·tcp/ip·安全·ip
UpUpUp……1 小时前
C++继承与组合完结
开发语言·c++·笔记
EasyCVR1 小时前
EasyRTC嵌入式音视频通信SDK:WebRTC技术下的硬件与软件协同演进,开启通信新时代
linux·运维·服务器·c语言·音视频·webrtc
C++ 老炮儿的技术栈4 小时前
设计模式,如单例模式、观察者模式在什么场景下使用
c++·笔记·学习·算法
不羁。。7 小时前
【操作系统安全】任务3:Linux 网络安全实战命令手册
linux·安全·web安全
Vitalia8 小时前
⭐算法OJ⭐二叉树的后序遍历【树的遍历】(C++实现)Binary Tree Postorder Traversal
开发语言·c++·算法·二叉树
飞鼠_8 小时前
c++简单实现redis
c++·redis·bootstrap
二进制人工智能9 小时前
【QT5 多线程示例】互斥锁
开发语言·c++·qt
流烟默9 小时前
编写脚本在Linux下启动、停止SpringBoot工程
linux·spring boot·shell