进程和环境变量的相关概念

一,进程

1. 进程的概念

       简单的说,一个执行中的程序就是一个进程。具体说, 进程是一个分配系统资源的活动实体,它包含以下两点:

(1)实体,每个进程都有一段地址空间,在这段空间中,用于存放程序的代码,数据,指令的地址等静态的事物;

(2)程序运行的过程,是一个动态的概念,即需不断申请占用系统资源的当前的活动过程。

总之,进程不仅包含有关程序的一些静态事物,还有需不断申请系统资源的动态的过程。

2. 进程的创建过程

进程形成的方法:

(1)一个程序要运行起来,首先要被加载到内存上,这时,就形成了进程。该进程的父进程是bush。

(2)通过系统调用来创建,在一个进程中利用fork,vfork函数创建子进程,该子进程的父进程就是创建它的的进程。(关于fork,vfork的使用,在后文中有详细说明)

形成之后:

        操作系统要对该进程进行管理,但并不和该进程的程序代码等直接接触。而是:

(1)描述:将该进程的相关属性信息描述起来,放在一个叫进程控制块的数据结构中,将进程控制块统称为PCB,在Linux中称PCB为task_struct,task_struct由操作系统创建。

(2)组织:操作系统将各进程的PCB利用链表的形式组织起来,放在内核中。这样,操作系统就可以对各进程进行统一管理了。

(3)为各进程分别创建一个虚拟地址空间(也是一个结构体:mm_struct),页表,映射关系。

        各进程的相关代码和数据都存放在物理内存中,但操作系统并不直接对物理内存进行操作,而是将虚拟地址通过页表映射到物理内存地址上,从而对其进行操作。该虚拟地址空间由上述的PCB中的指针指向,这样,操作系统便可管理该进程了。

        总之,一个进程的创建,操作系统需:

(1)描述并创建PCB,并将其链入链表组织起来放入内核

(2)创建虚拟地址空间,页表,映射关系

3. task_struct的内容

标识符PID:描述本进程的唯一标识符,用于区别其他进程,类似于学生的学号,人的身份证号。

状态:包括任务状态,退出代码,退出信号等。

优先级:一个CPU一次只能运行一个进程,运行哪个进程要根据其优先级来判断

程序计数器:存放程序中即将被执行的下一条指令的地址,类似于寄存器PC的存在

内存指针:包含程序代码和进程数据相关的指针,还有和其他进程共享的内存块的指针

上下文数据:进程执行时处理器的寄存器中的数据

I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备,被进程使用的文件列表

记账信息:包括进程存在的时间总和等。

其中:

进程的状态包含:

(1)R:运行状态,处于该状态的进程要么在运行要么处于运行队列中

(2)S:浅度睡眠状态,可以被中断,可以用kill命令杀死

(3)D:深度睡眠状态,不可被中断,不可被杀死,一般在I/O时发生,要结束该状态的进程,要么等I/O结束,要么关机

(4)T:停止状态

(5)Z:僵尸状态:进程退出后一直维持该状态,直到该进程的父进程读取了他的退出信息后,将其释放。

状态的优先级:CPU根据优先级来决定运行进程的先后顺序

        可以通过ps -l命令来查看优先级,其中:

PRI:表示进程的优先级,此值越小优先级越高

NI:优先级的修正数值

所以,PRI=PRI(80)+NI(-20~19)

可以通过命令来修改NI:

(1)renice:修改已存在进程的优先级:renice    NI的值   -p    要修改进程PID

(2)nice:修改启动前的进程的优先级:nice  -n   NI的值    可执行程序路径

        运行如下进程:

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

        执行如下命令:


        或者,在进程启动前,输入以下命令:


4. 查看进程

        在一个进程运行过程中,需要知道如何查看该进程是否被创建成功。

(1)Linux下输入命令:

ls /proc

        该命令用于显示进程目录/proc中所有当前正在运行的进程


        这些数字即为正在运行进程的PID。

(2)通过命令ps或top获取进程信息。

(3)利用系统调用获取进程标识符

pid_t getpid();//获取当前进程的PID
pid_t getppid();//获取当前进程的父进程的PID

这两个函数均需引用头文件<sys/types.h>和<unistd.h>

5. 创建进程

         在了解了进程的相关信息后,接下来,我们来创建一个进程。

(1)通过运行程序来创建

        注意:进程是一个动态的过程,程序在运行期间,进程被创建,一旦程序运行结束,进程也就退出了。

        那么,程序在一瞬间就运行结束,我们如何知道进程是否被创建呢?

        编写如下代码,使程序一直运行:

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

        编译链接运行该程序:


        我们发现该程序一直在运行,也就是该进程一直存在,但怎么获取该进程相关的信息呢?

        这时,需要该进程在后台运行,从而获取其相关信息,输入以下指令:


        输出的数字为该进程的PID,这时,查看/proc目录,可以看到该进程的PID在该目录中。说明该进程已经被创建成功,当输入:

kill -9 7856(进程的PID)

        可通过该命令杀死该进程,此时,在查看/proc目录时,已经没有该进程的PID了。说明该进程已经结束。

(2)利用fork函数创建子进程

        fork创建子进程后,子,父进程对fork语句之后的代码和没有修改的数据进行共享,如果数据被父或子进程修改,则在父,子进程中会各自开辟存放该数据的内存,采取写实拷贝。

        fork函数的返回值情况:

1)若子进程创建成功,向父进程返回子进程的PID,向子进程返回0;

2)若子进程创建失败,则返回-1。

    运行如下程序:

#include<stdio.h>     
#include<sys/types.h>
#include<unistd.h>                                                              
int main()
{
    int ret=fork();
    if(ret<0)
    {   
        printf("fork error!\n");
        return 1;
    }   
    else if(ret == 0)//子进程
    {   
        printf("child:pid=%d,ppid=%d,ret=%d\n",getpid(),getppid(),ret);
    }   
    else//父进程
    {   
        printf("farther:pid=%d,ppid=%d,ret=%d\n",getpid(),getppid(),ret);
    }   
    sleep(1);
    return 0;
}

        运行结果:


        可以看到,在子进程创建后,先运行父进程,在运行子进程,返回值情况确实满足上述两点。

注意:fork创建子进程后,谁先运行是不确定的。

(3)利用vfork创建子进程

        vfork的用法与fork相同,只是,有下列区别:

1)vfork创建子进程后,子,父进程共享代码和数据

2)先运行子进程,子进程只有通过调用exit或exec结束后,父进程才开始运行

6. 僵尸进程

        就是处于僵尸状态的进程,当子进程退出,父进程没有读取子进程的退出信息时,子进程就一直处于僵尸状态。

        运行以下程序来创建僵尸进程:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    int ret=fork();
    if(ret<0)
    {   
        printf("fork error!\n");
        return 1;                                                                                                                                            
    }   
    else if(ret>0)//父进程
    {   
        printf("farther:pid=%d,ppid=%d,ret=%d\n",getpid(),getppid(),ret);
        sleep(30);//30s后父进程退出
    }   
    else//子进程
    {   
        printf("child:pid=%d,ppid=%d,ret=%d\n",getpid(),getppid(),ret);
        sleep(5);//5s后子进程退出
        exit(1);
    }   
    return 0;
}

        在该程序运行5s内,子,父进程都在运行。5s后,子进程运行结束,此时,父进程还在运行,还没来得及获取子进程的退出信息,所以,5s后子进程变为僵尸进程;30s后父进程运行结束,子父进程均退出。

        输入以下命令,观察处于僵尸状态的子进程:

        进程开始运行后5s内:


        5s后:


        30s后:


7. 孤儿进程

        如果父进程先退出,而子进程还在进行,此时,子进程就会变成孤儿进程。 孤儿进程会被1号进程领养,该孤儿进程退出后,就会被1还进程回收。

        下面,演示一个孤儿进程的例子:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    int ret = fork();                                                                                                                                        
    if(ret == -1)
    {
        perror("fork error\n");
        return 1;
    }
    else if(ret > 0)//父进程,先结束
    {
        printf("pid = %d,ppid = %d ret = %d\n",getpid(),getppid(),ret);
    }
    else//子进程,30s后再结束
    {
        printf("pid = %d,ppid = %d ret = %d\n",getpid(),getppid(),ret);
        sleep(30);
    }
    return 0;
}

        在该例中,父进程执行完毕后,即退出,而子进程30s后才退出,所以,来观察下子进程的父进程的PID来验证它是否被1号进程领养。

        结果如下:


二,环境变量

        环境变量是操作系统中指定程序运行环境的一些变量,比如,在链接库文件时,这些库文件在哪里,去哪里找,这些信息都存放在环境变量中。

        环境变量通常具有某些特殊用途,在系统中通常具有全局特性。

1. 常见环境变量

(1)PATH:存放系统常用命令的搜索路径的变量

        查看该变量的内容,输入命令:

echo $PATH

        输出结果:

/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/admin/bin

        每个路径用分号相隔。比如,常用的命令ls,我们在运行它的时候,并没有加上的路径,并不知道该命令的可执行程序放在哪里,这时,操作系统,就会根据PATH中提供的路径,一条路径一条路径的找,直到找到为止。

        我们自己编写形成的可执行程序a.out在运行时,必须加上路径,如:./a.out,表示运行当前路径下的a.out。那如何能像运行ls一样不加路径呢,有如下两种方法:

1)将可执行程序a.out放在PATH指定的路径下,那么在运行时,就会根据PATH中的路径去找a.out所在的位置。

如:cp  ./a.out  /bin

2)将a.out的路径放入到PATH变量中。这时,需要先明白a.out的路径,再进行存放,输入命令:


        $PATH表示原有的内容,即上述查看PATH时输出的内容。 :/home/admin/bit_code/Linux表示在原有PATH的基础上追加该路径;然后把原有PATH的内容和追加后的路径一起赋给变量PATH;export是导出变量PATH,即更新PATH。

        再次进行查看时,便可看到a.out的路径已经在变量PATH中:


        若要删除新存放的路径,只需要关闭终端,再次打开即可。

        如果不想关闭终端来删除新追加的路径,只需输入以下命令即可:

export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/admin/bin

(2)HOME:指定用户的主工作目录(可以用echo命令来进行查看)

(3)HISTSIZE:系统中默认保存的历史命令的条数,一般是1000。(以同样的方法可查看)

        若要查看历史命令,只需输入命令:history

(4)SHELL:查看当前使用的shell

2. 常用的有关环境变量的命令

(1)echo:查看环境变量的内容,上述已经说明

(2)export:设置一个新的环境变量

        1)更新原有环境变量的值,(这里不加export也可以,因为该变量已经是环境变量,但最好加上)上述已经验证

        2)将本地变量变成环境变量。

        将MYENV设置为环境变量,在终端运行以下命令:

MYENY="hello world"

        用echo查看时,没有结果,说明该环境变量没有设置成功,他只是一个本地变量。

        再在终端运行以下命令:

export MYENY="hello world"

        用echo查看时,可以看到结果,说明该环境变量设置成功。

        所以,export的作用是设置环境变量。如果环境变量已经存在,可以理解为更新环境变量的值(也可以不加);如果环境变量不存在,就必须用export设置环境变量。

        注意:使用export设置的环境变量在关闭中断后即失效        

(3)env:查看系统中存在的所有环境变量及其值

(4)set:显示所有本地定义的shell变量及环境变量和其相应的值(注意与env的区别)

        运行以下命令:

MYENV="hello world"

        上述已经说明,该变量不是环境变量(用env查看没有结果),所以是本地变量,用set查看:

set | grep MYENV

        这时会显示结果:

MYENV='hello world'

        说明,set可以显示本地变量,同样可以显示环境变量,这里不再说明。

(5)unset:清除环境变量

        先用export设置环境变量MYENV,再用env查看是否创建成功,创建成功后,在用unset清除该环境变量,在用env查看是否清除成功。如下:


3. 环境变量的全局特性

        首先通过命令在终端设置本地变量:MYENV=“hello world”,然后通过以下程序(即子进程,bash为父进程)输出该环境变量的值。

        下面先介绍一个系统调用getenv

 #include <stdlib.h>//需引用的头文件
 char *getenv(const char *name);//函数原型,该函数用于获取指定环境变量的值,参数是环境变量的名称字符串

        再运行以下代码,输出环境变量的值:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    char *env=getenv("MYENV");                                          
    if(env)
    {   
    printf("%s\n",env);
    }   
    return 0;
}

        当运行该代码时,出现如下结果:


       发现没有结果,说明子进程不能看到本地变量的值。

       通过export将MYENV设置为环境变量,在运行程序时,会输出:hello world。说明,此时子进程能够看到环境变量的值。因为环境变量具有全局特性。

注意:本地变量没有全局特性,只会被本shell看到,不会被子进程看到。

          而环境变量具有全局特性,所以会被本shell及以下的所有子进程看到,即被子进程继承下去。

 






  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值