【Linux】-- 进程概念

目录

一、进程概念

二、PCB

1.什么是PCB 

2.什么是task_struct

3.task_struct包含内容

 三、task_struct内容详解

1.查看进程

(1)通过系统目录查看

(2)通过ps命令查看 

(3)通过top命令查看 

(4)通过系统调用获取进程PID和父进程PPID

        获取进程ID函数getpid和getppid

        获取当前进程ID

        获取父进程ID

2.状态 

3.优先级

4.程序计数器 

5.上下文数据

6.I/O状态信息

7.记账信息

四、通过系统调用创建进程

1.使用fork创建子进程

2.理解fork创建子进程

3.fork后的数据修改 

4.fork的返回值

(1)fork返回值含义 

(2)根据fork返回值让父子进程执行不同的功能

五、进程状态

1.进程状态定义

2.进程状态分类

(1)R-运行状态

(2)S-浅睡眠状态

(3)D-深睡眠状态

(4)T-停止状态

(5)Z-僵尸状态

(6)X-死亡状态

 3.僵尸进程危害

六、孤儿进程

七、进程优先级

1.概念

2.为什么要有进程优先级

3. 查看系统进程

4.PRI和NI

5.使用top命令更改进程优先级

(1)更改NI值 

(2)NI的取值范围 

(3)NI取值范围较小的原因

八、环境变量

1.概念

2.常见环境变量

3.如何查看环境变量 

4.和环境变量相关的命令

5.环境变量的组织方式

(1)环境表 

(2)获取环境变量

6.环境变量的全局属性

7.本地变量

九、程序地址空间

1.程序地址空间分布 

2.程序地址空间是虚拟地址

3.虚拟地址

(1)mm_struct 

(2)页表和MMU 

(3)进程地址空间存在的原因 

4.写时拷贝


一、进程概念

课本概念:进程是程序的一个执行实例,是正在执行的程序。

内核观点:进程是承担系统资源(CPU时间、内存)的实体。

当我们写完代码之后,编译连接就形成一个可执行程序.exe,本质是二进制文件,在磁盘上存放着。双击这个.exe文件把程序运行起来就是把程序从磁盘加载到内存,然后CPU才能执行其代码语句。当把程序加载到内存后,这个程序就叫做进程。所有启动程序的过程,本质都是在系统上创建进程,双击.exe文件也不例外:

二、PCB

1.什么是PCB 

 根据操作系统管理是先描述再组织,那么操作系统是如何描述进程的呢?先预想一下,肯定是先描述进程信息,然后再把这些信息用数据结构组织起来进行管理。那么进程都有哪些信息呢?使用

ps axj

命令查看系统当中的进程,也就是正在运行的程序:

看到进程的属性至少有PPID、PID、PGID、SID、TTY、TPGID、STAT、UID、TIME、COMMAND。

进程信息被放在一个叫做进程控制块PCB(Process Control Block)的数据结构中,它是进程属性的集合。

操作系统创建进程时,除了把磁盘上的代码和数据加载到内存以外,还要在系统内部为进程创建一个task_struct,是一个struct。

2.什么是task_struct

Linux操作系统的下的PCB就是task_struct,所以task_struct是PCB的一种,在其他操作系统中的PCB就不一定叫task_struct。

创建进程不仅仅把代码和数据加载到内存,还要为进程创建task_struct,所以进程不仅仅是运行起来的程序,更准确的来说,进程是程序文件内容和操作系统自动创建的与进程相关的数据结构,其实进程还包括其他内容,今天先说这两个。

操作系统对每一个进程进行了描述,这就有了一个一个的PCB,Linux中的PCB就是task_struct,这个struct会有next、prev指针,可以用双向链表把进程链接起来,task_struct结构体的部分指针也可以指向进程的代码和数据:

 所有运行在系统里的进程,都以task_struct作为链表节点的形式存储在内核里,这样就把对进程的管理变成了对链表的增删改查操作。

增:当生成一个可执行程序时,将.exe文件存放到磁盘上,双击运行这个.exe程序时,操作系统会将该进程的代码和数据加载到内存,并创建一个进程,对进程描述以后形成task_struct,并把插入到双向链表中。

删:进程退出就是将该进程的task_struct节点从双向链表中删除,操作系统把内存中该进程的代码和数据进行释放。

3.task_struct包含内容

task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程信息。那么task_struct具体包含哪些信息呢?

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

还有一些其他信息。下面解释task_struct包含内容的具体含义。

 三、task_struct内容详解

1.查看进程

(1)通过系统目录查看

proc是一个系统文件夹,在根目录下,通过ls可以看到该文件夹:

可以通过

ls /proc

命令查看进程的信息,数字是PID:

如果想查看进程信息,比如查看PID为989的进程信息,使用命令

ls /proc/PID

查看:

(2)通过ps命令查看 

 使用

ps aux

命令查看进程,可以看到所有进程:

 如果结合grep可以查看某一个进程:

比如想查看包含proc的进程,可以使用如下命令: 

ps aux | head -1 && ps aux | grep proc | grep -v grep

 

(3)通过top命令查看 

也可以通过

top

 命令查看:

(4)通过系统调用获取进程PID和父进程PPID

  • 获取进程ID函数getpid和getppid

获取进程ID和获取父进程ID可以通过以下方式进行获取,其中pid_t是short类型变量: 

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

pid_t getpid(void);//获取当前进程ID
pid_t getppid(void);//获取当前进程的父进程ID
  • 获取当前进程ID

 获取当前进程,process.c 

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

int main()
{
    while(1)
    {
        printf("hello linux!:pid:%d\n",getpid());//获取当前进程ID
        sleep(1);
    }

    return 0;
}

 Makefile:

process:process.c
    gcc -o $@ $?
.PHONY:clean
clean:
    rm -f process

运行之后,就获取到了当前进程的PID,即进程号: 

 关闭进程可以通过ctrl+c或者来关闭进程。另开一个窗口,现在通过ps来查看进程:

 这也就验证了getpid获取到的是PID。

  • 获取父进程ID

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

int main()
{
    while(1)
    {
        printf("hello linux!:pid:%d,ppid:%d\n",getpid(),getppid());
        sleep(1);
    }

    return 0;
}

 使用ps命令查看,发现父进程的ID是11081,但是11081同时也是bash的子进程:

 这是因为,运行命令行的命令有风险,命令行出错了,不能影响命令行解释,因此在命令行上运行的命令,基本上父进程都是bash。

 使用如下命令可查看到进程内部的所有属性信息:

ls /proc/当前进程ID -al

 当进程退出时,就没有/proc/18448这个文件夹了,ctrl c后,再去查看文件夹,已经不存在了: 

2.状态 

之前写代码的返回值是0 ,这个0是进程退出时的退出码,这个退出码是要被父进程拿到的,返回给系统,父进程通过系统拿到。比如以下代码的退出码是0

#include<stdio.h>
int main()
{
    printf("hello linux!\n");
    return 0;
}

 那么使用

echo $?

就可以查看到进程退出码为0: 

假如将退出码改为99 :

 那么程序运行后的退出码也变成了99:

 所以,状态的作用是输出最近执行的命令的退出码。

3.优先级

权限指的是能不能,而优先级指的是已经能了,有权限了,但是至于什么时候执行得先排队 ,这就像在餐馆点餐结帐出小票之后,已经可以拿到餐食了,但是什么时候能拿到呢?需要排队,在这个过程中,是否出小票就代表是否有权限,排队取餐就代表的是优先级。

4.程序计数器 

 当CPU执行程序时,执行当前行指令时,怎么知道下一行指令是什么呢?程序计数器pc中存放下一条指令的地址,当操作系统执行完当前行指令后,pc自动会++,直接执行下一行命令。

内存指针可以通过task_struct中的内存指针,通过PCB 找到进程的代码和数据。

5.上下文数据

当操作系统维护进程队列时,由于进程代码可能不会在很短时间就能执行完毕,假如操作系统也不会在执行一个进程时,让其他进程一直等待,直到当前进程执行完毕,那可能当前进程需要执行很久才执行完毕,其他进程会一直处于等待状态,这不合理。那么操作系统在实际执行进程调度时,按时间片分配执行时间,时间片一到,就切换下一个进程。时间片是一个进程单次运行的最长时间。

 比如有4个进程,在40ms之内先让第一个进程运行10ms,时间一到就算没有运行完毕,就把第一个进程从队列头移动到队列尾,再让第二个进程运行10ms。40ms后,使得用户感知到这4个进程都推进了,其实本质上是通过CPU的快速切换完成的。

有可能在一个进程的生命周期内被调度成百上千次。比如CPU有5个寄存器,进程A正在运行时时间片到了,被切走的时候,会把CPU里和进程A相关的保存到寄存器里面的临时数据带走。当进程B调度完后,再次调度进程A的时候,会把进程A里面保存的临时数据再恢复到CPU的寄存器当中,继续上次切走时的状态继续运行,因此保护上下文能够保证多个进程切换时共享CPU。 

6.I/O状态信息

 文件操作有fopen、fclose、fread、fwrite等函数,其实是进程在操作文件,因为在把代码写完之后,程序运行起来时,操作系统会找到这个进程,进程打开文件进行IO操作,其实IO都是进程在进行IO,所以操作系统需要维护进程和IO信息。

7.记账信息

记录历史上一个进程所享受过的软硬件资源的结合。

四、通过系统调用创建进程

1.使用fork创建子进程

fork用来创建子进程:

#include <unistd.h>
pid_t fork(void);//通过复制调用进程创建一个新进程。新进程称为子进程。调用进程称为父进程。

先看一个奇奇怪怪的代码:

forkProcess_getpid.c 

#include<unistd.h>
#include<stdio.h>

int main()
{
    int ret = fork();

    if(ret > 0)
    {
            printf("I am here\n");
    }
    else
    {
            printf("I am here,too\n");
    }

    sleep(1);
    return 10;
}                    

按道理来说,要么打印I am here,要么打印I am here,too。但是请看执行结果,发现两句话都打印了,也就是既执行了if又执行了else:

 再看代码:

#include<stdio.h>
#include<unistd.h>

int main()
{
    int ret = fork();

    while(1)
    {
            printf("I am here,pid = %d,ppid = %d\n",getpid(),getppid());
            sleep(1);
    }

    return 10;
}

 发现有两个pid和ppid:

这说明执行while死循环不只一个执行流在执行, 而是两个执行流在执行,每一行两个id都是父子关系。这是因为fork之后有两个执行流同时执行while循环。

可以看到bash 16202创建了子进程 16705,子进程又创建了子进程 16706:

评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值