11月1日笔记并发1_进程

0.问题引入
    程序 = 数据结构 + 算法

    数据:用来表示人们思维对象的抽象概念的物理表现叫数据
            而数据处理的规则叫操作(指令)

    对某一个优先数据集合所实行的,目的在于解决某一个问题
    的一组有限指令集合,称之为计算
   


    (1)顺序执行
        一个程序执行完毕之后,才能执行下一个程序
    
        例如一个程序分为三个步骤
            输入数据-》计算-》写回文件

        缺陷 CPU利用率非常低

    (2)并发执行
        把指令的执行过程,分成一下几个不同的步骤
        取指----》执行------>写回
        不同的步骤是由不同的硬件完成的

        这样就可以多个程序同时执行


        为了提高cpu的利用率,增加吞吐量“并发执行”
        现代操作系统为了能让程序并发执行,特定引入“进程”的概念


1.进程是个什么东西?
    进程是具有独立功能的程序关于某个集合上的依次运行活动

    test.c    -----> 源程序
    int main()
    {
        int a,b;
        int sum;
        scanf("%d %d",&a,&b);

        sum = a+b;
        printf("sum = %d\n",sum);
        return 0;
    }
    gcc test.c -o test  =>test 可执行程序

    ./test       =》进程


2.进程和程序的区别
    (1)程序是一个静态的概念(是指令的有限集,“程序文件”)
        进程是一个动态的概念
    
    (2)进程是一个程序的一次执行活动
        一个程序可以对应多个进程
    
    (3)进程是一个独立的活动单位
        进程是竞争系统资源的最小单位
e.g
    程序是一个菜谱
    进程是炒菜的过程

    OS为什么要引入进程呢》
    就是为能让程序执行并发(同一时段有多个进程在运行)
   

    进程是如果何让多个程序并发执行呢?

    程序的并发实际就是进程的并发。
    进程是如果同时运行,实现并发呢?


3.进程的状态
    OS把一个进程的执行过程,分为一下几个阶段
    就绪态(Ready)                              准备工作已经做好了,只要有CPU就可以执行
    运行态(running)                           CPU正在执行这个进程的指令
    阻塞态(Blocking 等待waiting)     进程在等待其他的外部事件

    进程可以在这几个状态进行切换
    
    就绪队列 Ready Queue
        所有处于Ready的状态的进程,都在一个这个队列中

        “调度策略”“:"调度算法"

            分时系统:调度策略是以“时间片轮转”为主要策略
                            “时间片轮转” 每个进程执行一段时间(时间片)
                             时间到了或者进程自己放弃就会进入到waitting
                
                    如:大部分桌面系统 windows android linux  macos unix...

                    

            实时系统
                    调度策略以实时策略为主要策略的系统
                实时策略:每次调度都去优先级最高的那个进程执行,
                                  直到这个进程执行完毕或者它主动放弃CPU
                                  或者有其他更高优先级的进程抢占
                    
                    如 ucos freeRTOS

                    进程1 优先级4  进程2 优先级3
                    跑进程1
                    进程1执行1秒中之后 主动放弃了CPU 进入waiting状态
                    跑进程2
                    进程1经过1s后,有进入到就绪队列
                    跑进程1 进程2会被打断

                    进程1 优先级4  进程2 优先级4
                    跑进程1
                    进程1执行1秒中之后 主动放弃了CPU 进入waiting状态
                    跑进程2
                    进程1经过1s后,有进入到就绪队列
                    跑进程2 进程2不会被打断
            
            "抢占" :插队 不论是实时还是分时系统都具有抢占的特性

    思考: OS引入进程后 是如何实现程序并发执行的

    程序执行的过程 =》进程
    程序执行和分配资源的最小单位 =》进程

    程序 =  数据 + 指令

    进程要做的第一件时间 就是申请一块内存空间来存储程序
        不同的程序数据的属性是不一样的,需要去分区域
        存储程序的数据。
    


4.linux进程地址空间布局
    “分段” 分成了不同的逻辑区域
    Linux对进程的数据仅分段管理,不同属性的数据存储的
    内存段也不同,不同的内存段(内存区域)的属性及管理的方法不一样

    .text
        主要是存放代码。
        只读且共享,这段内存在程序运行期间不会被释放的
        “代码段” 随进程持续性
    
    .data
        数据段
        主要存放程序中已经初始化的全局变量和已经初始化的static修饰的变量
        可读可写 这段内存在运行一直存在 随进程持续性
    
    .bss
        数据段
        主要存放程序中没有初始化的全局变量和没有初始化的static修饰的变量
        可读可写 这段内存在运行一直存在 随进程持续性
        .bss段在进程初始化时,(可能)全部初始化为0;
    
    .rodata
        只读数据段
        主要存放程序中的只读数据(常量)
        只读 随进程持续性

    栈空间(stack)
        主要保存局部变量(不是static修饰的局部变量)
        可读可写。时段空间会自动释放(变量所在的代码段执行完了,代码块中的局部变量
        的空间就是自动释放)随代码块持续性 返回局部变量的地址时有问题 也是这个原因

    堆空间(heap) 动态内存空间
    主要时malloc/realloc/calloc动态分配的空间
    可读可写,这段内存在进程运行期间,只要分配,就一直存在
    直到你手动free 或者进程消亡

    内存泄漏/垃圾内存


5.linux下进程相关的API
    (1)创建一个新的进程
        fork
         用户数据
            fork一个新进程的时候 这个新的进程的数据和指令来源哪里?
                来源它爸(父亲,调用fork那个进程)            fork这个函数在创建子进程时:
                copy    父进程的数据和指令
                            父进程的变量 数据对象
                            标准IO缓冲区
                            文件描述符
                            。。。
                copy完了后 父子就独立了
            
            fork成功时,会有两个进程在执行当前的代码!!!!
            所以为了区分父进程还是子进程 ,fork一次调用会有两个返回
                一个是父进程的返回
                一个是子进程的返回
            通过fork的不同的返回值 来区分到底是父进程返回还是子进程返回            

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

            pid_t fork(void);

            返回值
                如果失败返回-1 同时errno被设置
                如果成功
                        父进程返回  子进程的pid(>0)
                        子进程返回  0

    思考题
        fork一旦成功 就会有父子进程,那么fork之后到底是父进程
        先执行还是子进程先执行 不直到取决于调度算法

        fork子进程会拷贝附近的指令的数据 到底拷贝了那些数据呢?
            a:父进程全部的用户数据
            b:父进程打开的文件描述符及状态
            c:标准IO缓冲区
            d:信号的处理方式
            ....       fork用来创建一个新的进程(child process),   要创建新的进程
            首先要知道一个进程里面包含了一些什么东西
                指令
                系统数据


    linux系统会为每一个进程,分配一个唯一的进程ID(>0的整数),pid_t的类型来描述
    而且还提供了两个函数用于获取当前进程(自己)以及父进程的pid;    

        NAME
            getpid, getppid - get process identification

        SYNOPSIS
            #include <sys/types.h>
            #include <unistd.h>
            //获取当前进程的pid
            pid_t getpid(void);
            //获取父进程的pid
            pid_t getppid(void);


    (2)进程退出
    进程退出有两种情况
    (2.1)自杀(自己退出)
        a.main函数返回 进程退出
        b.在进程执行时,自己调用了进程退出函数
    (2.2)他杀 被操作系统给干掉了

        NAME
            exit - cause normal process termination

        SYNOPSIS
            #include <stdlib.h>
            void exit(int status);

             int status:退出码 表示退出时的状态
                退出码含义 由程序员解释 自己决定退出码的含义
                正常跑路 
                exit  正常退出 做清理工作(如把缓冲区的内容同步到文件中去)

        NAME
            _exit, _Exit - terminate the calling process

        SYNOPSIS
            #include <unistd.h>

            void _exit(int status);

         int status:退出码 表示退出时的状态
            退出码含义 由程序员解释 自己决定退出码的含义
         比如: 你走进传销了,得赶紧跑路
                     中止进程,来不及做清理工作

    


    (3)等待子进程退出

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

                pid_t wait(int *wstatus);

                pid_t waitpid(pid_t pid, int *wstatus, int options);

            这两个函数用来等待某个(些)子进程的状态方式改变的,等待的状态
            发生改变有三种情况
                a.子进程退出(正常退出:main函数结束  函数结束exit/_exit)
                b.子进程被信号终止
              //c.子进程被信号唤醒(waiting ->ready)
            
            在子进程正常退出的情况下,调用wait/waitpid可以释放子进程的资源,
            没有调用wait/waitpid,那么子进程退出后,就会变成僵尸进程
                一个进程的退出,操作系统会释放掉他大部分的资源,但是有一部分
                必须留给他父进程去释放,如果一个进程退出了,但是它父进程没有
                wait/waitpid,那么这个进程就会变成僵尸进程:已经死掉了,但是
                资源没有完全被释放

            如果一个子进程的状态已经发生了改变,那么调用wait/waitpid就会立即返回
            否则则阻塞调用进程,直到子进程的状态发生了改变或者信号中断

            wait: 用来等待任意一个子进程退出或状态方式改变的
                    如果一个附近有多个子进程,你需要多次调用
                    wait来等待所有的子进程结束

                pid_t wait(int *wstatus);

               int *wstatus:int类型指针 指向的空间用来保存子进程的退出信息
                                (退出码,怎么死的)
                    返回值
                        成功返回退出的那个子进程的pid
                        失败返回-1 同时errno被设置

             status: 用来保存子进程的退出信息,退出信息保存在一个整数上的。
                        WIFEXITED(status)
                            加入这个进程是正常退出(情况a) 则返回true
                            只有子进程正常退出 才会有退出码

                        WEXITSTATUS(status)
                                返回子进程的退出码,只有子进程正常退出
                                这个宏才有意义
                                退出码是当作unsigned char 来看待的
                        
                        WIFSIGNALED(status)
                            return ture 这个进程是被信号给干掉的

                pid_t waitpid(pid_t pid, int *wstatus, int options);

             pid_t pid: 指定要等待的进程或进程组状态发生改变

                        pid == -1 表示等待调用进程的任意子进程
                        pid == 0  表示等待与调用进程同组的任意进程
                        进程组: 就是一组进程。每一个进程必须会属于某一个进程组
                                    并且每个进程组都会有一个组长进程,一般来说,组长进程
                                    就是创建这个进程组的进程 进程组有一个组ID
                                    这个组ID就是组长进程ID
                        pid < -1 表示等待组id等于pid绝对值的那个组的任意子进程
                                如果 pid = -1234
                                        表示等待组id为1234哪个组的任意子进程
                        
                        pid > 0 表示等待指定的子进程 其进程id就是pid

    int *wstatus   status: 同上

           int options: 等待选项
                    0:表示阻塞等待
                    WNOHANG:非阻塞等待  如果加入子进程没有结束 则立即返回,不等待
                                        如果结束了,则就返回该进程的pid
                
                返回值 成功返回退出进程的pid 
                            失败返回-1 同时errno被设置

                wait(&status)  =>waitpid(-1,&status,0)
    


    (4)exec函数族
        fork一个进程 一般来说 目的是让进程去执行其他的任务

        exec函数族的主要功能是 让进程去执行指定的程序文件
            exec函数族让指定的程序文件中的数据和指令替换掉
                调用进程的数据和指令

        exec函数族让一个进程去执行另外一个程序 那么
                    要指定程序文件的名称
                    在文件系统中程序文件名称 (路径)
                    还得指定程序运行的参数
                linux下面程序的参数 都是字符串

            指定程序的参数有两种方式
                l:list
                    把程序运行的参数 一个一个的列举出来
                    程序运行的第一个参数 是程序的名称(不带路径)                    "sum","2","4",NULL
                
                v:vector 向量 数组
                    把程序运行的参数,弄成一个数组 char*
                    char *argv[] = {"sum","2","4",NULL}            

            NAME
                execl, execlp, execle, execv, execvp, execvpe - execute a file

            SYNOPSIS
                #include <unistd.h>

                extern char **environ;

                        execl 让进程去执行参数指定的程序文件
                int execl(const char *path, const char *arg, ...
                                /* (char  *) NULL */);

                  const char *path:你要执行程序的文件名(带路径)
                    const char *arg,..:程序运行的参数
                            如
                                "sum","2","4",NULL
                        
                        返回值 失败返回-1 同时errno被设置
                                    成功 就不会有返回,因为都替换了
                                指令和代码都被别人给替换了 哪来的返回值

                int execv(const char *path, char *const argv[]);

          char *const argv[]:指定程序运行的参数的数组 最后一个为NULL表示参数结束了

                    系统中有一个东西 环境变量 PATH
                    环境变量是整个系统环境内所有进程共享的变量
                    有很多个环境变量 其中过一个叫 PATH
                        PATH:=dir1:dir2:dir3

                    PATH的作用是 指定命令或程序的搜索路径。
                    当只指定一个命令或程序的文件名,而没有指定路径时
                    那么系统就先回去dir1下面找 如果没有找到 则去dir2....

                     如果程序文件或命令已经在PATH指定的搜索目录下了

                    指定文件的时候就没有必要指定路径

                int execlp(const char *file, const char *arg, ...
                                /* (char  *) NULL */);
                int execvp(const char *file, char *const argv[]);

              const char *file :要执行的程序文件名(可以不带路径)

        -------------
        fork() ->exec*

        有没有一步到位函数组


    (5)system

    NAME
       system - execute a shell command
        用来执行command指定的程序或命令
        system 会等待命令或程序执行完成
    SYNOPSIS
        
        #include <stdlib.h>

        int system(const char *command);

   const char *command:字符串 表示要执行的命令 和在终端上面敲的一样


总结
    进程的概念
        进程和程序的区别
    进程的状态

    调度策略
        实时系统
        分时系统

        抢占

        就绪队列
    
    fork的实现原理
        1.复制
        2.独立
    exec函数族



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值