Linux进程

Linux进程

基本概念

进程是程序的一个执行实例,是一个正在执行的程序。从内核来谈,进程是操作系统进行资源分配的最小单位。

那进程和程序的区别是什么呢?我来举个例子,我们对一段代码进行编译后就会生成一个可执行程序,这个可执行程序本质上是一个存在磁盘上的文件,当我们运行这个文件后,相当于我们把这个文件从磁盘上加载到了内存中,因为CPU只会处理内存中的信息,当这个文件加载到内存后,我们就称这个文件叫进程。而没有加载到内存前,我们称这个文件叫程序。

这么多的进程,我们操作系统是怎么进行管理的呢,这就是我们上个文章所说的先描述,后组织

程序控制块PCB

PCB(Process Control Block) 程序控制块存放的是每一个进程的信息,它是一种数据结构,我们可以简单的理解为进程属性的集合。

操作系统对每一个进程进行了详细的描述形成了一个个PCB(先描述),然后以链表的形式组织起来(后组织)。操作系统只需要拿到链表的头指针就可以访问所有的进程,操作系统管理各个进程就转变为管理这个链表。
在这里插入图片描述

创建一个进程、关闭一个进程、找到某个进程、修改某个进程就是对这个链表的增删查改。

task_struct

进程控制块是描述一个进程的,Linux操作系统是用C语言编写出来的,那它的进程控制块必然是用结构体实现的。

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

我们分为两种方法:

方法1.通过系统目录

在根目录下,有一个名为proc的系统文件

我们使用以下命令来查看进程信息,其中第一列的数字就是这些进程的PID,对应文件夹记录者各种信息。

[---@VM-8-4-centos /]$ ls /proc/

在这里插入图片描述

我们再使用以下命令来查看PID为1的进程信息。

[---@VM-8-4-centos /]$ ls /proc/1

在这里插入图片描述

方法2.使用命令ps

我们只单独使用ps命令的话会显示所有进程信息。

[---@VM-8-4-centos /]$ ps ajx

在这里插入图片描述

我们把ps和grep命令结合起来,可以显示我们想要看到的进程信息。

[---@VM-8-4-centos /]$ ps ajx | head -1 && ps ajx | grep 11

为什么不grep 1,因为包含数字1的进程信息太多了。

在这里插入图片描述

查看进程PID和PPID
  • PID: 进程的标识符

  • PPID: 当前进程的父进程的标识符

系统调用:getpid()|getppid()

我们通过一段代码来查看进程的PID和PPID。

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    while(1)
    {
        cout << "process : PID->" << getpid() << ", PPID->" << getppid()  << endl;
        sleep(2);
    }
    return 0;
}

在这里插入图片描述

有了PID,我们可以通过我们刚刚学习到的命令ps来查询这个进程到底是不是存在的。

因为我刚刚重新运行了程序,所以PID更换了。

[---@VM-8-4-centos fork1]$ ps ajx | head -1 && ps ajx | grep 449

在这里插入图片描述

fork创建进程

函数调用:fork: 创建一个子进程。

在这里插入图片描述

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    fork();
    while(1)
    {
        cout << "process : PID->" << getpid() << ", PPID->" << getppid() << endl;
        sleep(2);
    }
    return 0;
}

在这里插入图片描述

fork出来的子进程也有自己的PID,也能证明操作系统给每个进程都创建PCB。

那么,怎么才能让父子进程各自执行自己的代码呢?

fork的返回值

在这里插入图片描述

  • 创建成功,父进程中返回子进程的PID,子进程中返回0。
  • 创建失败,父进程中返回-1。

使用下面的代码,让我们更深入的体会一下。

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    pid_t id = fork();
    if (id > 0)
    {
        while (1)
        {
            cout << "我是父进程, PID->" << getpid() << endl;
            sleep(2);
        }
    }
    else if(id == 0)
    {
        while (1)
        {
            cout << "我是子进程, PID->" << getpid() << endl;
            sleep(2);
        }
    }
    else
    {
        perror("fork fail");
    }
    return 0;
}

在这里插入图片描述

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

我们刚刚在PCB中提到了,PCB是保存进程各种信息的地方,所以状态信息也在PCB中,也就是task_struct中。

我们可以使用以下命令来查看进程状态:

[---@VM-8-4-centos ~]$ ps ajx

在这里插入图片描述

或者使用

[---@VM-8-4-centos ~]$ ps aux

在这里插入图片描述

1 运行状态

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

可被调度的进程都被放在一个运行队列中,当需要时在运行队列中选取运行。

2 浅度睡眠状态

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

我们通过一段代码进行理解:

#include<iostream>
#include<unistd.h>
using namespace std;

int main()
{
    cout << "process : PID->" << getpid() << endl;
    sleep(100);
    return 0;
}

我们运行后得到PID是8209:

在这里插入图片描述

[wsj@VM-8-4-centos day01]$ ps aux | head -1 && ps aux | grep 8209

在这里插入图片描述

我们发现sleep后,进程确实是浅度睡眠状态。

3 深度睡眠状态

(DISK SLEEP)D-深度睡眠状态:有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。

一个程序若要求磁盘进行写入操作,在磁盘写入这个过程中,进程就会进入D状态。该进程会等待磁盘的回复以作出应答。

4 暂停状态

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

我们对刚才的程序进行发送SIGSTOP信号,我们可以发现它的状态从S变为了T
在这里插入图片描述

[wsj@VM-8-4-centos day01]$ kill -SIGSTOP 13251

在这里插入图片描述

我们再发送SIGCONT信号,则发现进程13251又变回了最开始的状态:

[wsj@VM-8-4-centos day01]$ kill -SIGCONT 13251

在这里插入图片描述

5 死亡状态

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

6 僵尸状态

Z-僵尸状态:僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

当一个进程将要退出的时候 在系统层面 该进程曾经申请的资源并不是立即被释放 而是要暂时存储一段时间 以供操作系统或是其父进程进行读取 如果退出信息一直未被读取 则相关数据是不会被释放掉的 一个进程若是正在等待其退出信息被读取 那么我们称该进程处于僵尸状态(zombie)。

僵尸进程
什么是僵尸进程

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

例如:

int main()
{
    pid_t id = fork();
    if(id > 0)
    {
        while(1)
        {
            cout << "我是父进程,PID->" << getpid() << endl;
            sleep(5);
        }
    }
    else if(id == 0)
    {
        cout << "我是子进程, PID->" << getpid() << endl;
        sleep(6);
        exit(2);
    }
    else
    {
        perror("fork fail");
    }
    return 0;
}

运行代码6秒后子进程会直接退出。子进程会等待父进程读取退出状态代码,但父进程还在运行,没有读取子进程状态码,子进程进入僵尸状态Z。

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

僵尸进程的危害
  • 因为子进程要告诉父进程退出信息,可父进程一直不读取,那么子进程就一直处于僵尸状态。
  • 僵尸进程的退出信息被存在task_struct(PCB)中,僵尸进程不退出,PCB就一直需要进行维护。
  • 如果父进程创建非常多子进程,但是不回收,就会造成资源浪费,因为数据结构对象本来就需要占用内存。
  • 僵尸进程申请的资源若不进行回收,那僵尸进程越多,可用资源就越少。这就是内存泄露。
孤儿进程
什么是孤儿进程

子进程退出,父进程一直不读取子进程退出信息,我们称它为僵尸进程。

如果父进程先退出,但是子进程还未结束,我们就称它为孤儿进程。如果没有父进程回收这个子进程,这个子进程就一直占用资源造成内存泄露。为了避免这种情况,当出现孤儿进程时,孤儿进程就会被1号init进程领养,当子进程进入僵尸状态时由init进行回收。

我们通过一段代码体会一下:

int main()
{
    pid_t id = fork();
    if (id > 0)
    {
        int count = 0;
        while (1)
        {
            cout << "我是父进程,PID->" << getpid() << endl;
            sleep(1);
            if (count == 5)
            {
                cout << "我是父进程,我要退出了" << endl;
                exit(2);
            }
            count++;
        }
    }
    else if (id == 0)
    {
        while (1)
        {
            cout << "我是子进程,PID->" << getpid() <<", PPID->"<< getppid()<< endl;
            sleep(1);
        }
    }
    else
    {
        perror("fork fail");
    }
    return 0;
}

在这里插入图片描述

如果需要关掉这个孤儿进程,我们只需要输入以下命令即可:

[---@VM-8-4-centos day01]$ kill -9 30470

这条命令的原理,我们在后面讲信号时会解释。

感谢您的阅读,欢迎指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值