并发 --------- 1、进程

一、什么是进程


1、程序 

    程序 = 数据结构 + 算法  

        数据:用来表示人们思维对象的抽象概念的物理表现 称为数据 

        指令:对数据的处理的规则 叫做指令  

        计算:对于有限的数据集合 所执行 目的在于解决某一个问题的一组有限指令的集合 称为计算  

    程序就是数据和指令的集合,一个程序的执行过程就是一个计算 


2、程序的执行方式 

    (1)顺序执行 


        一个程序执行完,才能执行下一个程序 
            例子: 
                一般来说 一个程序分为三个步骤: 
                    从键盘上获取数据 scanf() 
                    计算 
                    回显 printf()  
                缺点:CPU的利用率低,效率低 

    (2)并发执行 


        多个程序同时执行,互不影响 
            
            为了提高CPU的利用率,增加吞吐量 

        为了能够让程序并发执行,引入了进程的概念  
 

3、进程 

    进程: “进行中的程序”
        是具有独立功能的程序关于多个数据集合上的一次执行活动 

        例子: 
                gcc test.c  -o test       //test可执行程序 

                ./test                    
                            //  ps -ef 


4、进程 和 程序 的区别 

    1)程序是静态的概念 (指令的集合) 
       进程是动态的概念 (执行活动,动态产生,动态消亡)

    2)进程是程序的一次执行活动 
       一个程序可以对应多个进程 

    3)进程是一个独立的活动单位
        进程是竞争系统资源的基本单位 


5、进程的状态  

    操作系统把一个程序的执行过程,分为3个不同的状态 

        就绪态 Ready    准备工作已经做好了,只要等待CPU来执行
        运行态 Running  CPU正在执行该进程的指令 
        阻塞态 Blocked  等待 进程正在等待其他的外部事件 


            “就绪队列” 
                所有处于就绪态的进程 都在一个队列上排队等待CPU的调度   

            “调度程序” 
                任务调度,负责确定下一个进入 运行态的进程   

            “调度策略” 
                分时系统:调度策略 以“时间片轮转”为主要的调度策略 
                        让每一个进程执行一段时间 
                        Windows、 Linux 、... 

                实时系统:调度策略 以“实时策略”为主要调度策略
                        直到这个进程执行完毕 或者 主动放弃CPU 或者 被其他更高优先级抢占 
                        才会指向下一个进程


        进程要做的第一件事件 就是申请一块内存区域 来存储 程序的数据 
            不同的数据 属性是不一样 
            “分区域”来存储程序的数据


二、Linux进程地址空间的布局 

    “分段” 
        Linux对进程的数据进行分段管理,不同属性的数据,存放在不同的内存段中
            不同的内存段 其属性和管理也就不一样了 

    
    1)代码段  

        .text 主要存到代码(用户代码),包括main主函数在内的所有用户自定义的函数 
                 只读并且共享,这段内存 在程序的运行期间 不会被释放 

        .init 主要是存放系统给每一个用户自动添加的“初始化代码” 
                (例如:命令行参数、环境变量、... )

    2)数据段 

        .data 主要存放程序 已经初始化好了的全局变量 和 已经初始好了的 static变量 (静态变量)
                可读可写, 这段内存 在程序的运行期间 不会被释放 

        .bss  主要存放 未初始化的全局变量 和 未初始化的static变量 (静态变量)
                可读可写, 这段内存 在程序的运行期间 会被释放 
                .bss 在进程初始化的时候(可能)被初始化为0  (编译器的优化)

        .rodata  只读数据段read only
                主要存放程序中的只读数据(例如:字符串常量 )
                只读,这段内存 在程序的运行期间 不会被释放 

    3)栈 stack 


        主要存放 局部变量 (非static修饰的)
            可读可写,这段内存 会自动释放  
                    (代码块执行完毕,代码中的所有的局部变量的空间都会自动释放) 
            随代码块的持续性 

    
    4)堆 heap  


        动态内存空间 
        主要是 malloc / realloc / calloc 动态分配的空间  
        可读可写, 这段内存 在程序的运行期间,一旦分配成功,就会一直存在 
                直到 手动释放free() 或者 进程结束 
    

        练习: 
            int a;                      // .bss 
            int b = 9;                  // .data 

            int main()
            {
                char *s = "1234567890";     // s --> 栈         "1234567890" --> .rodata 
                char *s2 = malloc( 10 );    // s2 -->栈          s2指向的空间  --> 堆 
                int c = 4;                  // c --> 栈 
                static double d = 3.14;     // d --> .data 
                char buf[] = "abcdef";      // 栈 
                static char f;              // f --> .bss 
            }


         

三、Linux进程相关的接口函数 

    1)创建一个新进程 fork() 

        fork() 用来创建一个进程  
                pid_t 用这个类型来描述进程id 

            一个进程包含什么东西? 
                数据:用户、系统 
                指令

        当用fork()去创建一个新进程的时候,这个新进程的数据和指令来源于哪里? 
                来源于 父进程 

        ☆☆☆
            fork()这个函数在创建子进程的时候 
                拷贝父进程的 数据 和 指令 : 
                        父进程的变量、数据对象 
                        标准IO的缓冲区
                        文件描述符
                        ... 
                拷贝完毕,父子进程就独立了
                
                fork()成功之后,就会有两个进程执行当前的代码 
                    所以为了区分父子进程,fork()调用一次,会有两次返回 
                        一个父进程返回
                        一个子进程返回    

    NAME
            fork - create a child process
        SYNOPSIS
            #include <sys/types.h>
            #include <unistd.h>

            pid_t fork(void);
                功能:创建一个子进程 
                参数: 
                    无
                返回值: 
                    成功,返回 
                            父进程 返回子进程的pid    pid > 0 
                            子进程 返回0 
                    失败,返回 -1,同时errno被设置 

                    fork()函数内部实现的伪代码 
                        pid_t fork(void)
                        {
                            //拷贝,一旦拷贝成功,就会有两个进程往下执行 
                            clone();

                            if( 父进程 )
                            {
                                return 子进程pid ;
                            }
                            else if( 子进程 )
                            {
                                return 0;
                            }
                            else 
                            {
                                return -1;
                            }
                        }
            

        例子: 

            int main()
            {
                //创建一个子进程 
                pid_t pid = fork();
                if( pid > 0 )   //父进程 
                {
                    printf("I am father !\n");
                }
                else if( pid == 0 )  //子进程 
                {
                    printf("I am child !\n");
                }
                else 
                {
                    perror("fork error ");
                    return -1;
                }

                printf("We are family !\n");
            }

        注意: 
            1)命令行默认在父进程结束之后立即显示 
            2)父进程和子进程的执行的先后顺序是随机的

1.1、额外获取pid函数


        Linux会为每一个进程 分配一个唯一的进程id (>0) 整数 
                用 pid_t 类型 来描述 

            还提供了两个接口函数 用来获取 进程ID 和 父进程ID

                getpid()
                getppid() 

                    NAME
                        getpid, getppid - get process identification
                    SYNOPSIS
                        #include <sys/types.h>
                        #include <unistd.h>

                        pid_t getpid(void);
                            功能:返回当前进程的id 

                        pid_t getppid(void);
                            功能:返回父进程的id 

        
    2)进程退出 


        进程退出有两种情况 

        (2.1)自己退出 

            (a) main()主函数返回,进程结束

            (b)在进程的任意时刻,调用进程的退出函数 

                exit()  / _exit()  

                    NAME
                        exit - cause normal process termination
                    SYNOPSIS
                        #include <stdlib.h>

                        void exit(int status);
                            功能:终止进程 
                            参数: 
                                status:表示退出码,退出状态 
                                        退出码的具体的含义,由程序员自己来解释 (类似于函数的返回值)
                            返回值: 
                                无 

                        注意: 
                            exit正常退出,会做一下清理工作 
                                       (例如:把缓冲区的数据 同步到文件中 )

                    NAME
                        _exit  - terminate the calling process
                    SYNOPSIS
                        #include <unistd.h>

                        void _exit(int status);
                            功能:终止进程 
                            参数: 
                                status:表示退出码,退出状态 
                                        退出码的具体的含义,由程序员自己来解释 (类似于函数的返回值)
                            返回值: 
                                无 

                        注意: 
                            _exit直接终止,不会做清理工作 


        (2.2)他杀      


    3)等待子进程的退出  


        wait() 
        waitpid() 
            都是等待子进程退出,顺便帮子进程回收资源        

    NAME
                wait, waitpid - wait for process to change state
            SYNOPSIS
                #include <sys/types.h>
                #include <sys/wait.h>

                pid_t wait(int *wstatus);
                    功能:等待任意一个子进程退出 
                    参数: 
                        wstatus:指针,指向的空间用来保存子进程的退出信息(如何死亡、退出码等)
                    返回值: 
                        成功,返回退出的那个子进程的pid 
                        失败,返回 -1,errno被设置 

                        例子: 
                            int status;
                            wait( &status );

                    解析退出信息: 
                        WIFEXITED( status )   
                            如果表达式为真,则表示该子进程正常退出 

                        WEXITSTATUS( status ) 
                            返回子进程的退出码,只有在子进程是正常退出的情况下才有效

                        WIFSIGNALED( status ) 
                            如果表达式为真,则表示该子进程是被信号杀死的 

                        WTERMSIG( status )
                            返回 导致该子进程死亡的信号值 
 

                pid_t waitpid(pid_t pid, int *wstatus, int options);
                    功能:等待指定的子进程退出 
                    参数: 
                        pid:指定要等待退出的那个进程的pid 或者 进程组 
                                pid > 0 :等待指定的pid进程  

                                pid == 0   表示等待 与调用进程同组的任意子进程 
                                            “进程组” 
                                                就是每一个进程 都必须属于某一个进程组 
                                                创建这个进程组的进程为组长,进程组ID就是组ID
                                
                                pid == -1   表示等待 任意子进程  

                                pid < -1    表示等待 组ID 等于 pid的绝对值 的那个组里面的任意一个子进程
                                            例子: 
                                                pid == -123 
                                                等待组ID为123的进程组里面的任意一个子进程

                        wstatus:指针,指向的空间用来保存子进程的退出信息(如何死亡、退出码等)
                        options:等待选项 
                                0 :阻塞等待 
                                WNOHANG 非阻塞 如果没有可用的子进程,立即返回 
                    返回值: 
                        成功,返回退出的那个子进程的pid
                        失败,返回 -1,errno被设置 

            例子: 
                int main()
                {
                    //创建一个子进程 
                    pid_t pid = fork();

                    if( pid > 0 )   //父进程 
                    {
                        printf("I am father !\n");

                        int wpid;
                        int status;
                        //wpid = wait( &status );    //等待子进程退出
                        
                        //wpid = waitpid( pid, &status, 0 );    //等待子进程退出
                        wpid = waitpid( -1, &status, 0 );
                        printf("wait pid : %d\n", wpid );

                        if( WIFEXITED( status ) )
                        {
                            printf("exit code : %d\n", WEXITSTATUS( status ) );
                        }
                        else if( WIFSIGNALED( status )  )
                        {
                            printf("kill signal : %d\n", WTERMSIG( status ) );
                        }
                    }
                    else if( pid == 0 )  //子进程 
                    {
                        printf("I am child !\n");

                        sleep(5);
                        //return 2;
                        exit(5);
                    }
                    else 
                    {
                        perror("fork error ");
                        return -1;
                    }
                    printf("We are family !\n");
                }


        这两个函数都是 用来等待某个(某些)子进程的状态发生改变: 
            等待状态发生改变 有三种情况: 
                (1)子进程退出(正常退出): main主函数返回, exit(),  _exit() 
                (2)子进程被信号终止 
                (3)子进程被信号唤醒  
        
        注意: 
            假如一个子进程的状态的已经发生改变了,那么调用 wait()/waitpid() 立即返回,不会阻塞等待 
            否则 阻塞等待某个子进程的状态发生改变 或者 被信号中断 

        在子进程正常退出的情况下,调用 wait()/waitpid() 可以释放子进程的资源 
            如果没有调用 wait()/waitpid() ,子进程的资源就不会被释放,就会变为 “僵尸进程” zombie process  

                “僵尸进程” 
                    一个已经终止运行 但是 仍然占有系统资源的进程 称为僵尸进程 

                “孤儿进程” 
                    是指 其父进程执行完毕或者被终止后,仍继续运行的子进程 称为孤儿进程 
                    孤儿进程最终会被init进程收养,init进程会回收它们的资源

四、exec函数族  

    fork创建一个子进程,是让子进程去做其他任务 

    exec函数族 
        exec函数主要的作用 让一个进程去执行另一个指定的程序文件  
            也就是说,让指定的程序文件的数据和指令 替换 调用进程的数据和指令

            exec让进程去执行另外一个程序,需要告诉这个进程什么东西?
                    指定要执行的这个程序的路径名 
                    可能需要指定 程序运行的参数 (linux程序的命令行参数都是字符串)

                l   list 
                    把程序运行的参数 一个一个列举出来
                    程序运行的参数 第一个是程序的文件名 (不带路径的)
                    最后一个参数 必须是 NULL
                        "sum", "123", "456", NULL 

                v   vector数组
                    把程序运行的参数 都存放在一个char *数组中
                        char * buf[] = { "sum", "123", "456", NULL };

                p   path路径 
                    是否指定可执行文件的路径 
                    加p:不需要指定路径 

                e   environment环境变量 
                    传该程序的环境变量 
       

execl / execlp / execle / execv / execvp / execvpe 

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

            SYNOPSIS
                #include <unistd.h>

                extern char **environ;

                int execl(const char *path, const char *arg, ... /* (char  *) NULL */);
                    功能:去执行一个指定的程序文件 
                    参数: 
                        path:指定程序文件的路径名 
                        arg: 程序运行的参数 
                    返回值: 
                        成功,不返回 
                        失败,返回-1,同时errno被设置

                int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
                int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
                int execv(const char *path, char *const argv[]);
                int execvp(const char *file, char *const argv[]);
                int execvpe(const char *file, char *const argv[], char *const envp[]);


        例子: 
            sum.c  
                int main( int argc, char *argv[] )
                {   
                    int s = atoi( argv[1] ) + atoi( argv[2] );
                    printf( "sum = %d\n", s );
                    return s;
                }

            
            execl( "./sum", "sum", "123", "456", NULL );

                    =====================
                        atoi()  //字符串转整数 
                            NAME
                                atoi - convert a string to an integer
                            SYNOPSIS
                                #include <stdlib.h>
                                int atoi(const char *nptr);
                            例子: 
                                int x = atoi("123");
                                printf("%d\n", x);  // 123 

    ==============================================
        system()  用来执行指定的命令 
            NAME
                system - execute a shell command
            SYNOPSIS
                #include <stdlib.h>
                int system(const char *command);
                    功能:执行command指定的命令 
  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值