首页 » 技术分享 » 进程的基本概念

进程的基本概念

 
文章目录

1.什么是进程?

      1. 进程就是运行起来的程序,程序运行起来需要被加载到内存中。(这是站在用户的角度看待进程的)
      2. 进程就是操作系统的描述,这个描述叫PCB(进程控制块),Linux下PCB有自己的名字叫task_struct.而操作系统就是使用task_struct结构体描述进程,使用双向链表来将这些结构体组织起来进行管理。

task_struct结构体的内容分类(进程的描述信息)下面简单的介绍几类:

    进行(运行)中的程序------程序是死的。放在硬盘中,当运行起来>的时候就会被加载到内存中。        站在操作系统的角度:进程是PCB(进程控制块) ---task struct
                标识符:描述本进程的唯一标识符,用来区别其他进程的。
                状态:任务状态,退出代码,推出信号等。
                上下文数据:进程执行时处理器的寄存器中的数据。
                程序计数器:记录每个进程下一次要进行的指令的地址。
                优先级:相对于其他进程的优先级。

                文件的状态信息
                记账信息。

如果想深入的了解的话可以查看大佬的详解:https://blog.csdn.net/bit_clearoff/article/details/54292300

2.查看进程信息的命令

        ps -ef:查看所有的进程信息。
        ps aux:查看进程的详细信息。
        top;(查看进程的信息)
        查看你所要的进程信息: ps -ef | 名字。
                getpid();(在代码中获取一个进程的id)(一个系统的调用接口)

#include<stdio.h>

int main()
{
    printf("%d\n", getpid());//使用getpid()来获取一个进程的id
    return 0;
}

3.如何创建一个进程:

        shell---命令行解释器(shell相当于我们命令行的界面,如果我们从键盘中敲入一个指令时,shell就会执行这条指令)。

系统调用open返回的是一个文件描述符,类型为int,C库中的fopen返回的是文件流指针:FILE*。
我们所说的缓冲区(printf),用户态的一个缓冲区,是文件流指针自带的
    printf,  fwrite这些库函数都是先把数据写入到缓冲区中,等缓冲区写满了之后或者其他;条件满足之后才会写入到真正的文件中,而系统调用没有这个用户态的缓冲区(是直接将数据写入到文件中)这个说法不准确
fork() ---(通过复制调用生成一个新的进程)创建一个子进程。
                对于父进程来说,fork()的返回值是子进程的pid
                对于子进程来说,fork()的返回值是0

        因为子进程是根据父进程为模板来创建的,因此父子的代码段是一样的(父子进程运行的是同一段代码)。但是父子进程的返回值不同。父子进程的数据不相同。子进程的数据会另外开辟内存来存放。
        对于代码来说父子进程是相同的(相同的代码只是fork()之下的代码,而不是从头到尾),但是父子进程的数据是独有的(写时复制技术)。
        父进程是从代码头到代码的结束。子进程是从fork()开始到代码结束。
        用户就是通过返回值来判断分辨父子进程,来进行代码的分流。

        kill 进程号----》杀死进程。

4.进程的状态

        R(running)            运行态:并不意味着程序一定在运行中,它表明进程要么在运行中要么在运行队列里。
        S(sleeping)           可中断的休眠(浅度睡眠)
        D(disk sleep)         不可被中断的休眠,只能通过指定的方式--->唤醒(深度睡眠)
        T(stopped)            停止的状态
        t(tracing stop)       追踪状态
        X(dead)                 死亡状态
        Z(zombie)             僵死态

5.僵尸进程

        产生原因:
                子进程先于父进程退出它会保存自己的退出状态,因此它不会自动释放所有资源。子进程退出后会通知父进程子进程退出了,然后让父进程去获取退出状态,然后完全释放子进程资源。假如父进程不管子进程的退出状态,那么这个子进程就会变成一个僵死进程。
        僵尸进程的危害:资源泄露,正常的进程可能无法创建。
        僵死进程如何杀死:杀死它的父进程就可以了。

下面是一个僵尸进程的演示:

#include<stdio.h>
#include<stdlib.h>

int main()
{
        pid_t pid = fork();
        if(pid < 0)
        {
                perror("fork error");
                return -1;
        }
        else if(pid == 0)
        {
                printf("this is a child\n");
                sleep(10);//为了方便我们观察子进程退出之前的状态
                exit(0);
        }
        else
        {
                sleep(30);//为了使子进程先于父进程退出
                printf("this is parent\n");
        }
        while(1)
        {
                sleep(1);
        }
        return 0;
}

当子进程还没有退出的时候:此时的子进程和父进程都没有退出。(使用ps aux查看进程的状态)

 当子进程退出的时候:因为父进程还没有退出的时候,而子进程退出了,所以子进程就变成了一个僵尸进程。如下所示:(第一个是父进程,第二个是子进程(现在变成了僵尸进程))。

 上面这个就是一个僵尸进程。

6.孤儿进程

        产生的原因:
                父进程先于子进程退出,那么这个子进程就会变成一个孤儿进程,并且进入后台运行。
        特性:
                它原来的父进程退出后。它的父进程就变成了init进程,如果子进程退出后,init进程将负责释放子进程的资源,所以子进程就不会变成一个僵死进程。
        孤儿进程运行时,是后台运行,你可以继续输入其他的指令。

下面是孤儿进程的代码演示:

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

int main()
{
        pid_t pid = fork();
        if(pid < 0)
        {
                return -1;
        }
        else if(pid == 0)
        {
                sleep(10);//让子进程睡的时间长一点,让父进程先退出
                printf("this is child\n");
        }
        else
        {
                sleep(5);//这里睡几秒是为了执行后观察父进程退出之前的状态
                printf("this is parent\n");
        }
        return 0;
}

当父进程退出之前的状态:此时的父进程和子进程都没有退出:

当父进程先于子进程退出的时候:父进程因为退出而消失,子进程就变成了一个孤儿进程。

这就是一个孤儿进程。

7.进程的优先级

 为什么要有进程的优先级?

         因为进程的功能不同,因此进程对CPU的资源的要求也不同,所以对进程的调度就有了优先级,进程的优先级决定以一个进程的CPU资源的优先级分配权。

         cpu资源分配的先后顺序,就是指进程的优先权。

使用命令ps -l来查看

上面的一些列是什么意思呢?

        UID:代表执行者的身份
        PID:代表这个进程的id
        PPID:代表的是父进程的id
        PRI:代表这个进程可被执行的优先级,它的值越小优先级越高
        NI:代表这个进程的nice值。(表示进程可被执行的优先级的修正数据)      

其实进程的优先级最后等于PRI(New) = PRI(Old)+ nice  (所以当nice的值是负数的时候,优先级越高)。    PRI越小,越早执行。

修改进程的优先级命令:

        nice:可以指定进程(还没有运行起来的进程)的优先级。(nice -n 你要设置的nice值  程序)nice -n -10  ./test
        renice:也可以指定进程(已经运行起来的进程)的优先级。(renice 你要设置的nice值   -p   进程的id)renice -10 -p 5200

修改演示:首先编写一个死循环的程序,然后执行这个程序

nice 对进程的修改:

renice 对进程的修改:首先将程序执行起来,然后找到这个进程的id,然后用renice来修改优先级。

其他的概念:

        竞争性:系统的进程数目过多,而CPU只有少量,甚至只有1个,所以进程之间具有竞争的属性,为了高效的完成任务,更合理的竞争相关的资源,所以就有了优先级。
        独立性:一个进程出现问题不会影响其他的进程。多个进程运行期间互不干扰。
        并行:多个进程在多个CPU下分别同时运行,这称之为并行。
        并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

环境变量

        概念:环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。

查看环境变量的命令:env.

        环境变量保存操作系统环境相关的一些功能性的数据的变量。
        环境变量具有全局性,就是在哪里都可以找到这个环境变量。

环境变量的一些操作:

        1.通过main函数的第3个参数来获取环境变量。

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

int main(int argc, char *argv[], char *env[])
{
        int i = 0;
        for(i = 0; env[i] != NULL; i++)
        {
                printf("%s\n ",env[i]);
        }
    return 0;
}

        2.通过一个全局变量**environ来获取环境变量。

#include<stdio.h>
include<unistd.h>
int main(int argc, char *argv[], char *env[])
{
   extern char **environ;//存放环境变量的全局变量(因为它没有包含在头文件中,所以要声明)
   int i = 0;
   for(i = 0; environ[i];i++)
   printf("%s \n",environ[i]);
   return 0;
}

        3.getenv() ---》-获取指定的环境变量。(getenv("PATH")).
                          putenv()-----设置一个环境变量

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
        printf("%s\n", getenv("PATH");//用getenv来访问PATH这个环境变量
        return 0;
}

        4.export (设置一个环境变量)。

        5.set :查看一个环境变量。(它查看的环境变量比env更加的详细)
        6.echo :可以打印一个环境变量,在变量前加$.
        如何干掉一个环境变量:
             unset:删除一个环境变量(unset 环境变量的名字)

程序(进程)地址空间:

        首先它是一个线性的地址空间。(进程的虚拟地址空间)
        程序没有地址空间,因为程序在被激活运行起来之后才会有一个运行空间。

        内存地址就是对内存的一个编号,一个地址指向内存的一个位置。

那我们可以直接访问到物理内存呢?看下面的两段代码,思考为什么?

zone1.c

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

int val = 100;

int main()
{
        pid_t pid = fork();
        if(pid < 0)
        {
                return -1;
        }
        else if(pid == 0)
        {
                printf("child val:%d----%p\n",val);//在子进程中打印出val的值和它的地址
        }
        else
        {
                printf("parent val:%d----%p\n", val);//在父进程中打印出val的值和他的地址
        }
        return 0;
}

zone.c

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

int val = 100;

int main()
{
        pid_t pid = fork();
        if(pid < 0)
        {
                return -1;
        }
        else if(pid == 0)
        {
                val = 200;//在子进程中修改val的值,然后查看父进程中的val的值是否改变
                printf("child val:%d----%p\n",val);//在子进程中打印val的值和地址,
        }
        else
        {
                sleep(3);//这里让父进程先睡三秒,让子进程先执行。
                printf("parent val:%d----%p\n", val);//在子进程中打印val的值和地址,查看与父进程中的val和地址有什么区别。
        }
        return 0;
}

这两个代码段执行的结果为: 

这是就会产生一个问题:为什么在zone的执行结果中,父子进程的变量的值不相同,但是地址为什么相同呢?

        1.变量的内容不一样,代表父进程和子进程输出的变量绝对不是同一个变量

        2.地址一样,代表该地址一定不是物理地址。

其实进程的地址空间是一个虚拟的地址,并不是物理内存的地址。

一个虚拟地址如何找到真正的物理地址?

        其实虚拟地址和物理地址直接有一个页表的存在。

        页表:1.记录虚拟地址与物理地址之间的映射关系。
                   2.内存的访问控制(记录了属性的信息,比如:记录了该代码段是只读或者是只写的)。

页表也是一个结构体。

什么是虚拟地址空间:
        虚拟地址空间是一个结构体mm_struct结构描述的,结构体的名字叫struct_mm,因此程序地址空间应该叫虚拟地址空间。

程序的地址空间都是虚拟地址,而不是真正的物理内存地址,而访问虚拟地址空间是通过页表的转换后得到物理内存地址而访问内存的。

为什么计算机告诉我们每个进程都有4G的地址空间?
        因为计算机知道我们用不了这门多的地址空间,所以我们使用多少就通过页表给我们在物理内存中映射多少地址空间。

写时复制技术:
        假设有一个全局变量,那么在物理内存中就有一段空间来存放这个全局变量,如果父子进程不修改这个全局变量的话,那么父子进程的虚拟地址空间通过页表都指向同一块物理空间。如果父子进程修改这个全局变量的话,虚拟内存的地址不变,但是在物理内存空间会重新分配一块内存来保存你父子进程修改后的数据,并更新页表,这样也就是为什么你发现父子进程中一个变量它的虚拟地址相同但是值不同原因。

转载自原文链接, 如需删除请联系管理员。

原文链接:进程的基本概念,转载请注明来源!

0