Linux——进程概念

目录

1.进程的基本概念

2.进程的基本理解

3.描述进程-PCB

4.Linux-PCB

task_struct

task_ struct内容分类

组织进程

5.进程操作

查看进程

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

1.通过进程ID,我们便可以访问到进程得具体属性

2.通过系统调用,我们也可以获取进程的标识符

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

运行 man fork

fork基本用法—fork之后,代码父子共享

6.进程状态

操作系统得进程状态

Linux内核源代码

进程状态查看

R状态——S状态:

R+状态—S+状态

S状态—可中断睡眠状态

D状态—磁盘睡眠状态/深度睡眠/不可中断睡眠状态

T状态—暂停状态

t状态—调试暂停状态

Z(zombie)-僵尸进程

僵尸进程危害

孤儿进程


1.进程的基本概念

  • 课本概念:程序的一个执行实例,正在执行的程序等
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体。

其实,我们自己启动一个软件,将程序写入内存,本质就是启动了一个进程。

在Linux是可以同时加载多个程序的,Linux是可以同时存在大量的进程在系统中(OS,内存)

Linux系统需要管理进程,那么Linux是如果管理大量进程的呢?——先描述,在组织管理

2.进程的基本理解

根据操作系统的理解,可知,程序的本质就是文件,而内容就是 代码 + 数据

一个硬盘中可能存在很多程序——如下图

而调用这些程序,就需要先将他们写入在内存中,可知操作系统以及驱动程序也是软件,因此也在内存中,这些在内存中的程序就是进程。

 3.描述进程-PCB

大量的进程是如何进行管理的呢?此时引入PCB:        

为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。

可知PCB所有进程的属性,Linux内核是由C语言编写的,因此对进程属性的描述,就可以使用结构体的描述,而对进程的管理,就变为了对进程PCB结构体链表的增删改查。

如图:

综合由上可知,什么叫做进程:进程 = 对应的代码和数据 + 进程对应得PCB结构

PBC是进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

4.Linux-PCB

不同操作系统中,PCB得名字也是不同得

task_struct

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

Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园 (cnblogs.com)https://www.cnblogs.com/tongyan2/p/5544887.html

task_ struct内容分类

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

组织进程

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

5.进程操作

查看进程

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

1.通过指令 top 

2.通过指令 ls /proc 

 3.通过指令 ps axj 

大多数进程信息都可以使用 top 及 ps 结合grep和管道这些用户及工具进行获取

如:编写一个死循环得代码生成可执行文件,进行执行

此时进程中便会一直,存在这个程序,通过上述指令查看进程信息

 同时也可以在/proc中看到此进程

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

  • 进程id(PID)
  • 父进程id(PPID)

1.通过进程ID,我们便可以访问到进程得具体属性

 观察/proc 可知进程一旦运行便会生成对应得PCB,进程控制块 

其中 exe ,显示了程序得所处绝对路径

cwd,显示了调用程序所处得文件

每一个进程都会有一个属性来保存自己所在的工作路径

2.通过系统调用,我们也可以获取进程的标识符

 通过对.c文件编译执行,便可以通过系统调用的形式获取进程的标识符

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

运行 man fork

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

fork基本用法—fork之后,代码父子共享

int main()
{
    pid_t ret = fork();
    printf("pid : %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
        while(1)
        {
            printf("I am child,pid: %d, ppid: %d,ret: %d\n",getpid(),getppid(),ret);
            sleep(1);
        }
    }
    else
    { //father
        while(1)
        {
            printf("I am parent,pid: %d, ppid: %d,ret: %d\n",getpid(),getppid(),ret);
            sleep(1);
        }  
    }
    return 0;
}

 

 

通过指令while :; do ps axj|head -1 && ps axj|grep tFork2|grep -v grep;sleep 1; echo "#############################################################";done

更好的观察进程:

 

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

在语句ret=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(ret<0)……


  为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值

  1.   在父进程中,fork返回新创建子进程的进程ID;
  2.   在子进程中,fork返回0;
  3.   如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
  

fork出错可能有两种原因:

  1.   当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
  2.   系统内存不足,这时errno的值被设置为ENOMEM。

  创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
  每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
   fork执行完毕后,出现两个进程,

 两个进程的内容完全一样,为什么打印的结果不一样呢?那是因为判断条件的原因,上面列举的只是进程的代码和指令,但是变量两个进程的 ret 变量不同。 
  执行完fork后,进程1的变量为 ret != 0(父进程)。进程2的变量为 ret = 0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
  还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了前面的代码;fork只拷贝下一个要执行的代码到新的进程。

 参考文章:操作系统之 fork() 函数详解 - 简书 (jianshu.com)

6.进程状态

操作系统得进程状态

新建状态:实际并没有所谓的队列,其实就相当于刚把PCB创建拷贝出来,并还没有进入队列,此时就成为新建状态。

其实Linux内核中并没有这种状态,结构体创建拷贝完就需要立马执行,只是操作系统为了让理论完善,会有一些状态和操作系统实现状态会有一些差别

运行状态:task_struct 结构体在运行队列中排队,就叫做运行状态

阻塞状态:等待非CPU资源就绪,就叫做阻塞状态

挂起状态:当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,此时进程的状态就叫做挂起状态

 挂起阻塞:相当于进程正在等待某种资源,此时恰巧内存资源不足,将对应得代码和数据置换到磁盘上。

看看Linux内核源代码怎么说

  • 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。

下面的状态在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 */
};
  •  Linux操作系统的状态
  1. R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中,要么在运行队列里。
  2. S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))相当于阻塞状态。
  3. D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
  4. T停止状态(stopped): 可以通过发送 kill -19 PID, SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 kill -18 PID, SIGCONT 信号让进程继续运行。
  5. X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

进程状态查看

ps aux / ps axj 命令

R状态——S状态:

当我们写一个死循环,打印时,如下代码

#include<stdio.h>
#include<unistd.h>
int main()
{
    while(1)
    {
        printf("I am a process!\n");
    }
    return 0;
}

通过运行进程,查看进程状态,发现进程得状态为S+

 而再次修改代码,如下

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

再次运行进程,查看进程状态,发现进程得状态为R+

 为什么都是死循环,运行状态不同?

不要用自己得感受,去认为cpu得运行状态。cpu速度非常快。

第一段进程一定有运行状态,而显示器属于外设,外设速度相比cpu很慢很慢,当进程向显示器打印的时候,此时进程进入阻塞队列,而阻塞队列完成后,再次回到运行队列,循环往复,只是进程在阻塞队列相比在运行队列待得时间更长。

第二段进程,并没有进入阻塞状态,去访问外设,因此一直为运行状态

R+状态—S+状态

带加号表示前台进程

命令:./可执行程序 & 

表示将程序在后台运行——此时进程状态就是R状态——通过 kill -9 PID 命令杀掉进程

S状态—可中断睡眠状态

如下代码,强制进程睡眠100m

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

此时进程处于S+状态

 为什么又被称为可中断睡眠状态呢?

通过 kill 指令——kill -19 PID

 

 此时进程状态就变为了T状态

 可以看出,状态处于S时,可以给此进程发信号,此进程会做出对于得反馈,因此也成为可中断睡眠状态

D状态—磁盘睡眠状态/深度睡眠/不可中断睡眠状态

当服务器压力过大得时候,OS会通过一定得手段,杀掉一些进程,起到节省空间得作用!

而此时进程在向磁盘读写时,处于阻塞状态,不能被OS杀掉,此时进程就处于D状态

T状态—暂停状态

可以通过发送 kill -19 PID, SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 kill -18 PID, SIGCONT 信号让进程继续运行。

 发送 kill -18 PID, SIGCONT 信号

 

t状态—调试暂停状态

在调试代码时,打入断点,此时在run程序

进程便会进入到t状态

Z(zombie)-僵尸进程

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

一个进程已经退出,但是还不允许被OS释放,处于一个被检测状态,就叫做僵尸状态

一般一个进程结束后,会返回代码,一般是父进程或者OS维持此状态,为了让父进程和OS来进行回收。

创建维持30秒的僵尸进程例子:

#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;
}

僵尸进程危害

  • 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构 对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空 间!
  • 内存泄漏?是的!

孤儿进程

  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 父进程先退出,子进程就称之为“孤儿进程”
  • 孤儿进程被1号init进程(系统本身)领养,当然要有init进程回收。

孤儿进程示例

#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;
}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值