当前位置: 首页 > news >正文

Linux学习笔记之进程

进程的定义

  进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,也是操作系统结构的基础。
  例如当QQ程序运行的时候,计算机会先从磁盘读取QQ程序到内存,然后OS(操作系统)管理这个程序,这就是进程。简单来说进程就是OS处理进程时的数据结构加代码与数据。一般在Linux中这个数据结构就是task_struct结构体,本质就是链表。如下代码就是一个Linux进程结构体,里面有进程的状态,ID,分配的内存等。

struct task_struct {
    // 进程状态,例如:TASK_RUNNING, TASK_INTERRUPTIBLE 等
    volatile long state; 
    // 进程的进程号(PID)
    pid_t pid; 
    // 指向下个进程的 task_struct 指针
    struct task_struct *next; 
    // 进程的进程组号
    pid_t tgid; 
    // 进程的优先级
    int prio; 
    // 进程的虚拟内存描述符
    struct mm_struct *mm; 
    // 进程的内核栈指针
    void *stack; 
    // 进程的可执行文件信息
    struct files_struct *files; 
    // 进程的文件系统信息
    struct fs_struct *fs; 
    // 进程的信号处理相关信息
    struct signal_struct *signal; 
    // 进程的命名空间信息
    struct nsproxy *nsproxy; 
    // 进程的调度信息
    struct sched_entity se; 
    // 进程的运行时间统计
    u64 utime, stime; 
    // 进程的启动时间
    unsigned long long start_time; 
    // 进程的命令行参数
    char comm[TASK_COMM_LEN]; 
};

进程的目的与意义

  一般在电脑,手机上,我们不止要运行一个程序,会同时运行qq,杀毒软件,浏览器等。==如果我们不先描述进程的属性定义一个结构体,进程之间会产生干扰,那么OS就无法较好的管理内存。计算机就会是不是蓝屏,这对于用户来说十分的不友好。==为了给用户提供较好的使用环境,操作系统就使用进程的概念管理程序。

进程采用链表的原因

  在使用QQ的时候,我们会打开他,不用的时候可以关闭它,或者让他保持后台运行,因此对于进程而言,就有大量的增删操作,如果我们采用数组的方式就会造成数据处理慢,时间复杂度为O(N),也就是使用起来变卡了。
  其次使用数组就必须提前分配一大段连续的地址空间,但可能OS不需要如此大的空间,就会造成资源浪费,其次在数组扩容的时候,计算机可能没有足够大的连续空间,就会造成内存不够。采用单链表的方式就更加的灵活,充分利用散落空间。增删操作的时间复杂度是O(1),也不会造成大面积的内存荒废,想要使用就申请方便.

  进程就是运行的程序,在Linux中被描述为task_struct结构体,用链表进行管理。在Linux中可以用ps命令查看进程的信息,利用man查看其详细信息。

man ps

在这里插入图片描述
  可以通过XShell来访问服务器.ps ajx命令来查看进程,如下图
在这里插入图片描述

1.进程相关的函数

1.1getpid()

	man getpid

  在Linux中可以用上述命令查询该函数。
在这里插入图片描述

  在操作系统中有多个进程,为了区别他们,就引入了类似于身份证号的概念,每个进程都有自己唯一的pid(process Identification),当程序运行起来时就是一个进程,因此可以在程序中获取当前pid然后打印出来

 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 
  5 int main()
  6 {
  7 
  8     pid_t id=getpid();
  9 
 10     printf("我的ID是%d\n",id);                                                                                  
 11 
 12 
 13     return 0;
 14 }
~

运行程序,便可以得到如下ID。

在这里插入图片描述

1.2getppid()

	man getppid

  在Linux中可以用上述命令查询该函数。如下图
在这里插入图片描述
  在Linux中-bash就是命令行程序,我们输入的ls,mkdir命令在被bash解析后都会转化为一个进程,此时bash就是父进程,ls就是子进程,在子进程内调用getppid就可以得到父进程的pid。
  当我们打开多个命令行时便会有多个bash,如下图。
在这里插入图片描述
  我们可以通过如下例子证明任何进程是由父进程创造的,
在这里插入图片描述

ps ajx | head -1 && ps ajx | grep bash

  上述命令可以分为两个命令,用&& 连接,ps ajx | head -1 ,表示显示ps ajx结果的第一行,也就是状态栏。
在这里插入图片描述
  ps ajx | grep bash表示在进程信息中查询有关键词bash的,此时会有两个符合条件,第二个时命令行bash进程自然可以被检索到,而第一个仔细看其实是grep进程。grep是我们在命令行输入的指令,在经过bash解析后生成一个进程。此时grep的父进程ID就是32095,恰好就是bash的ID。
在这里插入图片描述

1.3 fork

  既然进程是由父进程创造的,那我们平时写的test.c文件在编译运行后也是一个进程,就可以由这个进程在创造出子进程,Linux就提供了fork函数建立子进程。
在这里插入图片描述
  在man介绍中返回值最重要,如下图
在这里插入图片描述
  成功时,在父进程中会返回子进程的进程 ID(PID),而在子进程中会返回 0。失败时,在父进程中返回 -1,不会创建子进程,并且会适当地设置 errno。

  于是我们便可以通过下属例子证明子进程是由父进程建立的。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{

    pid_t id=fork();
    if(id == -1)
    {
        printf("子进程创建失败\n");
    }

    if(id == 0)
    {
        //子进程
        while(1)
        {
            printf("我是子进程,PID为%d,PPID为%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else 
    {
        //父进程
        while(1)
        {
            printf("我是父进程,PID为%d,子进程ID为%d\n",getpid(),id);
            sleep(1);
        }

    }


    return 0;
}

在这里插入图片描述

1.4结束进程

  在Linux中可以使用ctrl+c结束进程,如下图。按下ctrl+c后进程就结束了
在这里插入图片描述
  也可以使用kill -9 +ID结束进程。其中-9表示结束信号,

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

  如下图使用kill -9在新开的命令行中结束父进程
在这里插入图片描述

2.进程属性

  在Linux中为了方便管理进程,就设置了多种属性,有如下状态。

* 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 */
};

  Linux要管理进程就必须遵循一个原则先描述再组织,即将进程各种属性封装再结构体内部,在通过队列组织起来,这样对于进程的管理就转化为对于数据结构的管理。在Linux中通常以固定的时间片来刷新进程,即Linux为了保证每个进程公平,当一个进程运行固定的时间后就切换到下个进程,依次循环往复。对应于队列就是头删尾插。

2.1 R(运行状态)

  运行状态实际上就是进程正在内存中的运行队列中,此时进程就在CPU中不断切换运行。

  1 #include<stdio.h>
  2 
  3 int main()
  4 {
  5     while(1)
  6     {                                                                                 
  9     }
 10     return 0;
 11 }

  让上述进程进入到死循环中,就会一直运行,再查看进程就可以看到R(运行状态)。此时proc进程就一直在内存中的运行队列中。
在这里插入图片描述

2.2 S(休眠状态)

  根据之前的冯诺依曼架构可以知道,CPU只与内存进行数据交换,由于磁盘等输出设备读取速度相对于内存过慢,CPU不会与输出设备例如显示器,磁盘,进行数据交换,而是要通过内存间接交换
在这里插入图片描述
  当进程要与外部设备输出时,这个消耗的时间对于CPU来说时巨大的,为了避免资源的浪费,此时进程就同时处于硬件的等待队列和内存中的运行队列,当进程在硬件获取到对应的资源,达到相应目的之后就将进程状态改为R运行状态。。在进程等待硬件资源的时候,进程的属性就是休眠状态。如下例子

#include<stdio.h>
  2 
  3 int main()
  4 {
  5     while(1)
  6     {
  7         printf("我是一个进程\n");
  8                                                                                                  
  9     }
 10     return 0;
 11 }

  在上述的代码中仅仅加入一条打印语句,”进程就处于S休眠状态“,说明此时进程被移动到显示器的等待队列中了,进程准备向显示器打印文字。但这个程序是个死循环,一定会有再次加载到CPU中的时候,也就是R状态,但是由于CPU运行速度十分快,刚好查询到R状态概率十分小

在这里插入图片描述

for i in $(seq 1 1000); do ps ajx | head -1 && ps ajx | grep ‘<proc>’ | grep -v grep; done

  运行上述脚本,在尝试数分钟后终于也是找到了那一瞬间为R的状态。这也再次证明了CPU的速度远远超过硬盘与内存。

在这里插入图片描述

2.3 D(深度休眠状态)

  这个状态十分少见,仅在系统濒临崩溃的边缘才可以看见。假如现在进程A里存储了1000W人口信息资料,此时要保存到磁盘中,那么给他状态设置为S,此时OS(操作系统)在管理进程,内存严重的不足了,OS的使命是保护系统正常运行,他看见A进程是S状态,又占用大量的内存,就把A进程强制结束了(就像手机打开应用闪退一样)。此时如果磁盘正常存储结束也就没什么问题,但是D磁盘内存也可能不足,此时磁盘向进程A返回错误信息,发现A进程没了,直接人傻了。十分重要的数据就发生了丢失,这是决定不允许的。
  于是便引入了D状态(磁盘休眠/深度休眠),操作系统不可结束该状态的进程。==系统可以崩溃,但是数据不可以丢失。==二者取其轻,选择相对可以接受的选择。

2.4 T(停止状态)

  在Linux中可以使用kill向进程发送信号。如下图
在这里插入图片描述
  其中19号信息就可以暂停进程。如下图

在这里插入图片描述

kill -18 16394

  然后18号信号可以再次启动进程。

在这里插入图片描述

2.5 t(追踪停止状态)

  在Linux中经常会使用gdb进行调试,虽然不怎么好用哈。当我们在程序中打断点,并且r运行到断点时,此时程序就是t状态。

gcc -g -o test test.c #加上-g表示加上调试信息
gdb test

在这里插入图片描述
  此时按r运行到断点的时候,程序就是追踪暂停状态。
在这里插入图片描述

2.6 X(死亡状态)

  死亡状态就是进程彻底结束,资源全部释放的时候,是一瞬间的状态,不能被检测到。

2.7 Z(僵尸状态)

  僵尸状态就是进程没有完全释放的状态,但是进程不在运行了。进程可以理解为是内核数据结构(task_struct)+代码和数据,当子进程完成任务时,就会先释放代码加数据,但保留task_struct供父进程判断检测子进程是否完成任务。其实死亡状态也就是将最后的tast_struct也释放完毕的瞬间状态。

  ls对于命令行解释器bash来说是其的子进程,当ls进程执行完时,返回的退出状态可以用==echo ¥?==显示出来,如下图。
在这里插入图片描述
  ls执行错误时也会返回对应的错误码,然后bash根据错误码打印对应信息。
在这里插入图片描述
  如果僵尸进程不被父进程管理回收那么他将永远是僵尸进程,如果父进程不管理僵尸进程的话,由此便有可能造成内存泄漏,也可以通过下面例子认识。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{

    pid_t id=fork();
    if(id == -1)
    {
        printf("子进程创建失败\n");
    }

    if(id == 0)
    {
        //子进程
        while(1)
        {
            printf("我是子进程,PID为%d,PPID为%d\n",getpid(),getppid());
            sleep(1);
        }
    }
    else 
    {
        //父进程
        while(1)
        {
            printf("我是父进程,PID为%d,子进程ID为%d\n",getpid(),id);
            sleep(1);
        }

    }


    return 0;
}

  运行上述程序并且使用kill结束子进程,但此时父进程没有做任何处理,那么子进程就是僵尸状态,如果父进程一直不处理那么就会造成内存泄漏。

在这里插入图片描述
  刚才是父进程和子进程都存在的情况下,结束子进程那么子进程是僵尸进程。如果结束父进程的话那么子进程就会操作系统领养称之为孤儿进程。(ps:操作系统的pid为一)。

在这里插入图片描述

  在超级用户下,查看进程1信息,发现其实就是操作系统

在这里插入图片描述

3 进程优先级

3.1 进程优先级意义

   CPU的资源具有稀缺性,进程优先级是进程获得CPU资源的一种先后顺序,即竞争力,就像我们下课时去食堂排队打饭一样,先到的他的优先级就越高就越先打到饭吃饭。

3.2 修改优先级

  Linux优先级由两部分组成一部分是priority(默认优先级),一部分是nice(微调优先级),进程的真实优先级等于priority+nice。在Linux中修改进程的优先级只能修改nice不能修改默认优先级。
在这里插入图片描述

3.2.1 top修改优先级

  按照以下步骤操作修改,top -> r ->pid -> nice值
在这里插入图片描述
在这里插入图片描述
  需要注意的是普通用户禁止频繁修改优先级, 如果被系统弹出禁止,需要切换到超级用户或者用sudo执行top。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
  在这里需要注意的是nice的值的范围是从-20到19,输入超过这个范围的值会自动适配到这个范围内。并且这个priority优先级是最初的优先级加上修改后的nice,而不是上一次的priority加上nice值
在这里插入图片描述

3.2.2 命令行修改优先级

renice <nice值> -p <进程ID>

在这里插入图片描述
  也可以修改为负数。在原来八十的优先级上减十就得到了现在七十的优先级.
在这里插入图片描述

3.3进程优先级调度

  进程一定是在运行队列之中,但不同的进程可以有相同的优先级说明运行队列一定不是一个简单的运行队列。实际上它是类似于哈希表的开链结构,每个都存储一个指针相当于单链表的头节点,那么当遇到相同优先级的时候就可以头插入对应下标单链表中,并且时间复杂度为O(1),它可以不断加入相同的优先级如下图.
在这里插入图片描述

相关文章:

  • 深度学习笔记——循环神经网络之LSTM
  • 用deepseek学大模型03-数学基础 概率论 随机变量 概率分布
  • 用deepseek学大模型04-模型可视化与数据可视化
  • Java ArrayList(单列集合)
  • Edge浏览器翻译|自动翻译设置
  • 网页模板免费HTML源码 HTML网页设计模板
  • DeepSeek 深度解析:引领 SEO 与数据分析新时代的智能工具
  • Oracle VirtualBox虚拟机软件中安装ubuntu(不理想版本)
  • 前端知识速记:BFC与IFC
  • STM32 RTC 实时时钟说明
  • 蓝桥杯单片机大模板(西风)
  • Java 大视界 -- 边缘计算与 Java 大数据协同发展的前景与挑战(85)
  • linux-带宽性能压测-全解iperfwgetspeedtest-cli
  • 【第9章:计算机视觉实战—9.4 计算机视觉在其他领域的应用探索】
  • 2021年下半年软件设计师下午试卷题型和考点总结(附真题及答案解析)
  • AI大模型的技术突破与传媒行业变革
  • 语音识别(实时语音转录)——funasr的详细部署和使用教程(包括实时语音转录)
  • Unity CommandBuffer绘制粒子系统网格显示
  • 【漫话机器学习系列】094.交叉熵(Cross-Entropy)
  • 【LeetCode】15.三数之和
  • 北京亦庄启动青年人才创新创业生态示范区
  • 铁路迎来节前出行高峰,今日全国铁路预计发送旅客1870万人次
  • 白玉兰奖征片综述丨国产剧集创作的此消彼长
  • “ChatGPT严选”横空出世了,“DeepSeek严选”还要等多久?
  • 游客曝九寨沟打网约车被出租车围堵,官方:前者违规,后者做法不对
  • 出行注意防晒补水,上海五一假期以多云天气为主最高33℃