美文网首页
四、进程、进程控制

四、进程、进程控制

作者: 木鱼_cc | 来源:发表于2018-07-20 22:12 被阅读0次

    1.进程相关概念

    1.1程序和进程

    程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(cpu、内存、打开的文件、设备、锁....)

    进程,是一个抽象的概念,与操作系统原理联系紧密。进程是活跃的程序,占用系统资源。在内存中执行。(程序运行起来,产生一个进程)

    程序 → 剧本(纸) 进程 → 戏(舞台、演员、灯光、道具...)

    同一个剧本可以在多个舞台同时上演。同样,同一个程序也可以加载为不同的进程(彼此之间互不影响)

    如:同时开两个终端。各自都有一个bash但彼此ID不同。

    1.2并发

    并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。

    例如,当下,我们使用计算机时可以边听音乐边聊天边上网。 若笼统的将他们均看做一个进程的话,为什么可以同时运行呢,因为并发。

    时分复用cpu
    1.3单道程序设计

    所有进程一个一个排对执行。若A阻塞,B只能等待,即使CPU处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。

    1.4多道程序设计

    在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。

    时钟中断即为多道程序设计模型的理论基础。 并发时,任意进程在执行期间都不希望放弃cpu。因此系统需要一种强制让进程让出cpu资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。 操作系统中的中断处理函数,来负责调度程序执行。

    在多道程序设计模型中,多个进程轮流使用CPU (分时复用CPU资源)。而当下常见CPU为纳秒级,1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

    1s = 1000ms, 1ms = 1000us, 1us = 1000ns 1000000000

    实质上,并发是宏观并行,微观串行!

    1.5CPU和MMU
    CPU MMU
    1.6进程控制块PCB

    我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
    /usr/src/linux-headers-3.16.0-30/include/linux/sched.h
    文件中可以查看struct task_struct 结构体定义。其内部成员有很多,我们重点掌握以下部分即可:

    • 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
    • 进程的状态,有就绪、运行、挂起、停止等状态。
    • 进程切换时需要保存和恢复的一些CPU寄存器。
    • 描述虚拟地址空间的信息。
    • 描述控制终端的信息。
    • 当前工作目录(Current Working Directory)。
    • umask掩码。
    • 文件描述符表,包含很多指向file结构体的指针。
    • 和信号相关的信息。
    • 用户id和组id。
    • 会话(Session)和进程组。
    • 进程可以使用的资源上限(Resource Limit)。
    1.7进程状态

    进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

    进程状态

    2.环境变量

    环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:

    ① 字符串(本质) ② 有统一的格式:名=值[:值] ③ 值用来描述进程环境信息。

    存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。

    使用形式:与命令行参数类似。

    加载位置:与命令行参数类似。位于用户区,高于stack的起始位置。

    引入环境变量表:须声明环境变量。extern char ** environ;

    练习:打印当前进程的所有环境变量

    #include <stdio.h>
    
    extern char **environ;
    int main(void)
    {
        int i;
        for(i = 0; environ[i] != NULL; i++){
            printf("%s\n", environ[i]);
        }
        return 0;
    }
    
    环境变量
    2.1 常见环境变量

    按照惯例,环境变量字符串都是name=value这样的形式,大多数name由大写字母加下划线组成,一般把name的部分叫做环境变量,value的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:

    PATH

    可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:
    echo $PATH

    SHELL

    当前Shell,它的值通常是/bin/bash。

    TERM

    当前终端类型,在图形界面终端下它的值通常是xterm。xterm是一个X Window System上的终端模拟器,用来提供多个独立的SHELL输入输出。终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

    LANG

    语言和locale,决定了字符编码以及时间、货币等信息的显示格式。

    HOME

    当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。

    2.2getenv函数

    获取环境变量值

    #include <stdlib.h>
    char *getenv(const char *name);     
    成功:返回环境变量的值;失败:NULL (name不存在)
    
    2.3setenv函数

    设置环境变量的值

    int setenv(const char *name, const char *value, int overwrite);     
    成功:0;失败:-1
    参数overwrite取值:  
    1:覆盖原环境变量 (改变只是在该进程中修改,进程完成后和进程开始前环境变量是不变的) 
    0:不覆盖。(该参数常用于设置新环境变量,如:ABC = haha-day-night)
    
    2.4unsetenv函数
    删除环境变量name的定义
    int unsetenv(const char *name);     
    成功:0;失败:-1 
    注意事项:name不存在仍返回0(成功),当name命名为"ABC="时则会出错。
    

    3.进程控制

    3.1fork函数
    创建一个子进程。
    pid_t fork(void);   
    失败返回-1;成功返回(两个):① 父进程返回子进程的ID(非负)   ②子进程返回 0 
    pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
    注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个
    
    3.2getpid函数
    获取当前进程ID
    pid_t getpid(void); 
    
    3.3getppid函数
    获取当前进程的父进程ID
    pid_t getppid(void);
    

    区分一个函数是“系统函数”还是“库函数”依据:
    1 是否访问内核数据结构
    2 是否访问外部硬件资源
    二者有任一 → 系统函数;二者均无 → 库函数

    练习

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>//exit
    
    int main(void)
    {
        pid_t pid;
        pid = fork();//执行fork() 就出现分支了
    
        if (pid == -1 ) {
            perror("fork");
            exit(1);
        } else if (pid > 0) {
            printf("I'm parent pid = %d, parentID = %d\n", getpid(), getppid());
            //getpid()获取当前进程ID,getppid()获取父进程ID
          
        } else if (pid == 0) {
            printf("child  pid = %d, parentID=%d\n", getpid(), getppid());
        }
    
        printf("-----------------finish-----------------------\n");//finish出现了2次
        return 0;
    }
    
    3.4循环创建n个子进程

    一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎样实现呢?

    简单想,for(i = 0; i < n; i++) { fork() }即可。但这样创建的是N个子进程吗?

    循环创建N个子进程

    从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n)-1个子进程,而不是N的子进程。需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break;才正确。

    练习:通过命令行参数指定创建进程的个数,每个进程休眠1S打印自己是第几个被创建的进程。如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程”。

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
        int n = 5, i;               //默认创建5个子进程
    
        if (argc == 2) {    
            n = atoi(argv[1]);
        }
    
        for (i = 0; i < n; i++) //出口1,父进程专用出口
            if (fork() == 0)
                break;          //出口2,子进程出口,i不自增
    //------------------------------------------------------------在这之前fork()出5个进程,只有fork()!=0的,才能与i具增
        
        //用循环因子i区分父进程、子进程
        if (n == i) {
            sleep(n);
            printf("I am parent, pid = %d\n", getpid());
        } else {
            sleep(i);
            printf("I'm %dth child, pid = %d\n", i+1, getpid());
        }
        return 0;
    }
    
    3.5子进程和父进程的关系

    关于资源:子进程得到的是除了代码段是与父进程共享的以外,其他所有的都是得到父进程的一个副本,子进程的所有资源都继承父进程,得到父进程资源的副本,既然为副本,也就是说,二者并不共享地址空间。两个是单独的进程,继承了以后二者就没有什么关联了,子进程单独运行。(采用写时复制技术)

    关于文件描述符:继承父进程的文件描述符时,相当于调用了dup函数,父子进程共享文件表项,即共同操作同一个文件,一个进程修改了文件,另一个进程也知道此文件被修改了。

    相关文章

      网友评论

          本文标题:四、进程、进程控制

          本文链接:https://www.haomeiwen.com/subject/pcpppftx.html