【Linux】对Linux操作系统中进程的理解

产品


目录

sql优化

一、冯·诺依曼体系结构(硬件方面)

mysql 索引使用与优化

举个例子加深理解

比特币

二、操作系统(软件方面)

adb 驱动获取

1、操作系统的理解

WebSocket使用案例

2、系统调用和库函数的概念

人工神经网络

三、进程控制块PCB的概念

禅道

四、进程的概念

MySQL读写分离

1、什么是进程?

面向对象编程

2、进程如何管理?

自定义模块

3、进程相关指令

makefile

3.1查看进程

旅游管理系统毕业设计

3.2杀掉进程

Linux 下,一切皆文件

3.3查看/proc文件系统

前端工具

4、系统调用

心跳机制与丢包重传

4.1子进程getpid()

谷歌支付

4.2父进程getppid()

资料

4.3创建子进程fork()

Oracle数据库SQL优化

五、进程状态的理解

视频编解码

1、什么是运行队列

卷积神经网络

2、对于CPU和硬件的速度差异,系统如何调度?

人体工程学

3、对于过多的阻塞进程,内存占用如何处理?

python安装

六、Linux操作系统的进程状态

IO流

1、进程状态在kernel中的定义

植被类型分布

2、进程的运行状态 

3、进程的浅度睡眠状态(阻塞状态的一种)

4、进程的深度睡眠状态(磁盘休眠状态)(阻塞状态的一种)

5、进程的暂停状态/追踪暂停状态(阻塞状态的一种)

6、进程的僵尸状态/死亡状态

7、孤儿进程

七、进程的优先级

1、进程优先级的概念

2、nice值的修改

八、进程切换

1、进程特性

2、进程切换(并发)


一、冯·诺依曼体系结构(硬件方面)

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

1、冯·诺依曼体系结构的存储器指内存,掉电易失。

2、而磁盘是一种外存,可以永久性存储数据。磁盘也属于外设的一种。外设又分为输入设备和输出设备。磁盘和网卡既是输入设备又是输出设备。

3、对于中央处理器,它有自己的指令集,外部程序翻译为CPU的指令集,让CPU根据这些指令集去执行。

4、 为了保证读取和写入速度,CPU只和内存打交道。

举个例子加深理解

现在我用我的电脑向我朋友的电脑传输一份压缩包文件。根据冯·诺依曼体系结构,将会经过以下流程:

二、操作系统(软件方面)

操作系统是管理软硬件的软件。

1、操作系统的理解

1、操作系统通过持续获取数据,进行软硬件的管理;

2、操作系统的管理的方法是先描述,再组织。先对被管理对象进行抽象成类,再根据这个类定义一个个具体的对象,再将这些对象通过数据结构进行关联,将软硬件的管理转换成对数据结构的管理。这其实也是面向对象的思想。

3、数据的采集和操作系统命令的下达,由驱动来做。

2、系统调用和库函数的概念

系统调用:从开发的角度,操作系统对外会表现为一个整体,但会暴露部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。

库函数:用户直接使用系统调用很困难,所以有了对系统调用进行封装的库函数。

三、进程控制块PCB的概念

1、我们写的可执行程序并不包含任何进程的属性信息。这些进程信息被放在一个叫做进程控制块的结构体中,用数据结构形成关联,可以理解为进程属性的集合;

2、该结构体被称之为PCB(process control block),Linux操作系统下的PCB是: task_struct;

四、进程的概念

1、什么是进程?

进程=内核数据结构(task_struct)+进程对应的磁盘代码和数据。

进程在调度运行的时候,就具有动态属性。

2、进程如何管理?

先描述,再组织。

操作系统会给每一个进程创建一个PCB对象,这些进程控制块对象用链表形成连接,通过遍历PCB的方式来找到对应状态的进程进行执行。例如找到优先级最高的PCB所对应的进程进行执行或通过PCB找到已死亡的进程进行释放。

通过这种方式,操作系统对进程的管理就变成了对进程所对应的PCB的管理,即链表的增删查改!

3、进程相关指令

3.1查看进程

ps axj | grep 'myproc'

能够显示出所有有关myproc的进程信息。

ps axj | head -1 && ps axj | grep 'myproc'

这个指令可以带上进程的小标题。

3.2杀掉进程

kill -9 8833

这里的8833是目标进程的PID。

3.3查看/proc文件系统

ll /proc/

/proc目录存在于内存中,这些数字名字的目录就是进程对应的PID。

ll /proc/14456 -d

能够找到正在运行中的PID14456的进程。

如果进程在运行时,可执行文件被删除,进程不会停止,但是进程目录中该可执行程序的路径将会无效。

4、系统调用

4.1子进程getpid()

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>                              
int main()    
{    
    while(1)    
    {    
        printf("hello world,MyPID=%d\n",getpid());
        sleep(1);
    }                
    return 0;
}  

调用getpid函数后,会将进程的PID打印出来

4.2父进程getppid()

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>                              
int main()    
{    
    while(1)    
    {    
        printf("hello world,MyPID=%d,MyPPID=%d\n",getpid(),getppid());
        sleep(1);
    }                
    return 0;
}  

可以发现,这个进程的父进程就是bash。命令行上启动的程序,一般它 的父进程没有特殊情况的话,都是bash。bash通过派生子进程的方式执行程序,如果程序有bug退出了,那挂掉的仅仅是子进程,bash没有影响。

4.3创建子进程fork()

#include <stdio.h>      
#include <unistd.h>      
#include <sys/types.h>      
int main()      
{    
    pid_t id=fork();    
    printf("MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);                                                                                                               
    return 0;                                 
}  

这个代码的执行结果是两个打印。

再看:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{    
                                                                    
    pid_t id=fork();                                                
    if(id==0)                                                       
    {                                                               
        //子进程    
        while(1)    
        {                                                                                                                       
            printf("子进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);    
            sleep(1);
        }                                                                      
    }                                                                                                  
    else if(id>0)                                                                                      
    {                                                                                                  
        //父进程                                                                                       
        while(1)                                                                                       
        {                                                                                              
            printf("父进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);      
            sleep(1);
        }                                                                                              
    }                                                                                                  
    else                                                                                               
    { 
    }
    return 0;
}

父子进程不断循环。说明fork()之后会有父子两个进程,fork()之后的代码,父子进程共享。

五、进程状态的理解

进程在不同的队列中,表示不同的状态。

1、什么是运行队列

1个CPU只有一个运行队列,进程的执行需要排队。让进程入队列,本质上是将task_struct结构体对象放入运行队列中。

这个进程队列中的指针可以找到需要加载的进程,将其PCB对象的信息加载到CPU中,CPU可以根据PCB对象的信息找到进程对应的代码进行执行。

在CPU的运行队列中的进程状态就叫做运行状态

状态,是进程的内部属性,存放于task_struct中。这个状态可以理解为一个整数,例如这个整数为1代表运行;2代表停止,3代表死亡状态等······

2、对于CPU和硬件的速度差异,系统如何调度?

进程不仅仅会占用CPU资源,也会占用硬件资源。对于CPU,它可以很快的处理进程的请求;但是对于硬件,速度很慢,例如网卡,可能同时有迅雷、百度网盘、QQ等进程需要获取网卡的资源,所以每一个描述硬件的结构体中也有一个task_struct* queue运行队列指针,指向排队中的PCB对象的头结点。

那么CPU和硬件的速度差异巨大,系统该怎么平衡这种速度?当CPU发现运行状态的进程需要访问硬件资源时,会让该进程去所需访问的硬件的运行队列中排队,CPU继续执行下一个进程。

那么这个被CPU剥离至硬件运行队列中的进程状态被称为阻塞状态。当进程对硬件的访问结束后,进程的状态将会被修改为运行状态,即该进程重新回到CPU的运行队列。

3、对于过多的阻塞进程,内存占用如何处理?

硬件的速度较慢,但是大量的进程需要访问硬件,势必会产生较多的阻塞进程,这些阻塞进程的代码和数据在短期内不会被执行,如果全部存在于内存中将会导致内存占用。

对于这个问题,如果内存中有过多的阻塞状态的进程导致内存不足,操作系统会将其的代码和数据先挪动至磁盘,仅留PCB结构体,以节省内存空间,这种进程状态被称为挂起状态。将进程相关数据,加载或保存至磁盘的过程,称为内存数据的换入和换出。

进程的阻塞状态不一定是挂起状态,部分操作系统可能会存在新建状态挂起或运行状态挂起等。

六、Linux操作系统的进程状态

1、进程状态在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 */
};

2、进程的运行状态 

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

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

状态后面有+号的表示前台进程,没有+号表示后台进程。前台进程在执行时,用户无法继续输入指令除非ctrl+c终止程序;后台进程在执行的过程中,用户可以输入指令且ctrl+c无法杀掉该进程。可以使用kill -9 PID的指令杀掉该进程。

3、进程的浅度睡眠状态(阻塞状态的一种)

浅度睡眠状态是可以被终止的进程状态。

#include <stdio.h>    
int main()    
{    
    int a=0;    
    while(1)    
    {    
        printf("%d\n",a++);               
    }                                  
    return 0;                          
} 

虽然数值一直在打印,但是printf函数需要访问显示器,大部分时间在等显示器IO就绪,只有小部分时间在执行打印代码。所以该代码呈现睡眠状态。

需要访问外设的,一般属于睡眠状态。

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

那么有人会问了,为什么我的grep进程也处于S状态?博主试过多次执行该指令,grep进程有小概率会处于R状态,其实这个和他所查询的进程的状态也有关系,我在查询的进程在摸鱼,那我也去摸会鱼吧~

4、进程的深度睡眠状态(磁盘休眠状态)(阻塞状态的一种)

只有在高IO的情况下才会发生(Linux中有一个dd命令可以模拟高IO的状态,可以动手试逝)。

D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的

进程通常会等待IO的结束。

在该状态下的进程,无法被操作系统杀掉,只能通过断电或者进程自己醒来的方式中断深度睡眠状态。

5、进程的暂停状态/追踪暂停状态(阻塞状态的一种)

可以输入kill -19 PID来让一个进程进入暂停状态。或者将一个任务从前台切换到后台等,进程均会变为暂停状态。

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

我们在使用gdb对一个可执行文件调试的时候,程序运行到断点处,程序会进入t追踪暂停状态(tracing stop):表示该进程正在被追踪。

6、进程的僵尸状态/死亡状态

Z僵尸状态(zombie):为什么会存在僵尸状态?因为进程在退出的时候,不会立即释放该进程对应的资源,会保存一段时间,让父进程或操作系统来读取子进程的返回代码。那么进程怎样进入僵尸状态?

模拟子进程正常退出,父进程不回收子进程(不读取子进程的返回信息)的场景(也可以使用kill -9 PID杀掉子进程):

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <stdlib.h>    
int main()    
{    
    
    pid_t id=fork();    
    if(id==0)    
    {    
        //子进程    
        while(1)    
        {    
            printf("子进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);    
            sleep(1);    
            exit(1);    
        }    
    }                                                                                                                        
    else if(id>0)      
    {                          
        //父进程    
        while(1)      
        {  
            printf("父进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);
            sleep(1);   
        }
    }
    else 
    {
        perror("fork");
        exit(-1);                                                                                                            
    }
    return 0;
}  

通过该命令循环打印进程信息,发现子进程已经变成了僵尸状态。旁边的<defunct>译为死者,进程已经死亡,但是还未被回收,这就是僵尸状态。

僵尸进程的退出结果会写在PCB中,一个进程退出了,它的代码和数据会被释放,但是它的PCB是不会被释放,如果父进程不回收这块资源,那么会造成系统的内存泄漏。那我能不能手动杀掉这个僵尸进程来手动释放僵尸资源?不可以,因为僵尸进程已经死亡,无法手动杀掉进程。

在Z状态的进程被回收后,进程状态变为X死亡状态(dead):父进程读取完子进程的返回信息后,收尸速度太快了,我们看不到,进程死亡状态立马被它的父进程回收。

7、孤儿进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{

    pid_t id=fork();
    if(id==0)
    {
        //子进程
        while(1)
        {
            printf("子进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);
            sleep(1);
        }    
    }    
    else if(id>0)    
    {
        //父进程    
        while(1)                                                                         
        {    
            printf("父进程,MyPID=%d,MyPPID=%d,%d\n",getpid(),getppid(),id);    
            sleep(1);    
        }    
   }    
    else 
    {
        perror("fork");
        exit(-1);                                                                        
    }
    return 0;
}

前台创建的子进程,父进程被杀掉后,父进程立马被bash回收。子进程被1号进程(操作系统)领养,同时切换到后台运行(使用kill -9 PID杀掉后台进程)。因为不领养子进程的话,那么子进程退出时呈现的僵尸状态就没有谁能回收了(内存泄漏)。这种被领养的进程称为孤儿进程

七、进程的优先级

1、进程优先级的概念

进程的优先级本质就是PCB中的一个整数数字(不同操作系统可能由多个数字决定)。

使用ps -la命令显示出当前的进程信息。PRI(priority)代表优先级的意思(priority默认是80);NI(nice)用于调整优先级(nice默认是0)。

进程最终优先级=默认优先级(固定80)+nice值。

Linux支持进程在运行过程中调整优先级,调整的方式是修改nice值。

2、nice值的修改

注:1、需要root权限2、使用r调出修改nice值的命令栏。

nice的取值范围为【-20,19】,数字输的再小再大也没用。

所以Linux中进程的权限范围为【80-20,80+19】,数字越小,优先级越高。

进程优先级不要人为的调整,如果一个进程的优先级较高或较低,可能会造成其他进程获取操作系统资源不均,造成操作系统自身的调度失衡。

八、进程切换

1、进程特性

竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级 。

独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰 。

并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行 。

并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

2、进程切换(并发)

cpu中有一个eip寄存器(PC指针),指向下一条指令的地址。

进程在运行的时候,占有CPU,会产生很多的临时数据,归属于当前进程。虽然CPU内部仅有一套寄存器硬件,但是寄存器中保存的数据属于当前进程。(寄存器是共享的,但是数据是各进程私有的)

进程在运行的时候都有自己的时间片,这个时间一到,即使进程还没有被执行完毕,但是会被操作系统剥离CPU,腾出CPU让下一个进程上来跑一跑。

那么这个进程下次再回到CPU继续运行时,操作系统是如何知道这个进程的代码被执行到哪里了?

首先,进程在切换的时候,需要进行上下文保护,一些临时数据被保存至PCB里(误);进程在恢复运行的时候,要进行上下文的恢复,后续该进程回到CPU运行时,将加载这些数据。通过PC指针继续运行下一行代码。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注