进程控制编程
一、进程
    1、进程和程序
        进程是一个程序的执行过程。它与程序有本质的区别:程序是静态的,它是一些保存在磁盘上的指令
    的有序集合。进程是动态的,他是程序执行的过程,包括动态调度和消亡的整个过程。是执行和资源管理的
    单位。Linux中,每个进程在创建时都会被分配一个数据结构,称为进程控制块PCB(process control
    block)。PCB包含了进程的描述信息、控制信息以及资源信息,它是进程的一个静态描述。PCB中的进程ID
    (process ID)被称作进程标识符,是一个非负的整数,在系统中唯一的标识一个进程。
        一个或多个进程可以合起来构成一个进程组(process group),一个或多个进程组可以合起来构成一个
    会话(session)。这样就有了对进程进行批量操作的能力,如通过向某个进程组发送信号来实现向该组中的
    每个进程发送信号。
    
    查看系统当前有多少进程正在运行可以使用命令:ps aux
            a:显示其他用户启动的进程
            x: 显示系统中属于自己的进程,
            u:显示启动这个进程的用户和启动时间
    
    2、进程的状态
        执行状态:该进程正在执行,即进程正在占用CPU
        就绪状态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片
        等待状态:进程不能使用CPU,等待时间发生则可将其唤醒
        
    3、进程的表示
        在linux中最主要的进程标识有进程号PID和它的父进程号PPID,获取标识符可以使用以下两个函数:
        getpid(): 获得进程的PID(process ID)
        getppid(): 获得进程的PPID(parent process ID)

二、linux进程编程
    1、创建进程
    fork函数:用户从已存在的进程(父进程)中创建一个新进程(子进程)。
              fork创建的子进程是父进程的一个复制品,复制内容包括父进程的执行状态,即子进程将会从父
              进程当前执行的命令的下一条命令开始执行。
    fork函数原型:

        pid_t fork(void)

        
        返回值:父进程中返回子进程的进程号,一个大于0的整数,而子进程则返回0,出错返回-1.
                因此,可以通过返回值判断该进程是父进程还是子进程。
        fork出错可能有两种原因:1、当前的进程数已达上限  2、系统内存不足
    
    2、exec函数族
        exec函数族提供了一个在进程中启动另一个进程执行的方法,并且调用exec并不创建新进程。
    exec函数原型:

        int execl(const char * path, const char * arg, ...)
        int execv(const char * path, char * const argv[])
        int execle(const char * path, const char * arg, ..., char * const envp[])
        int execve(const char * path, char * const argv[], char * const envp[])
        int execlp(const char * file, const char * arg, ...)
        int execvp(const char * file, char * const argv[])

        
        这些函数若调用成功,则加载新的进程从启动代码开始执行,不再返回,否则返回-1.(出错才有返回值)
        exec + l(list,将新进程的每个命令行参数都作为一个参数传入,最后一个参数为NULL,起标记作用
             + v(vector,构造一个指向各参数的指针数组,将首地址传入,数组最后一个指针为NULL)
             + e(environment,传入新的环境变量表,其他exec函数仍使用当前的环境变量表执行新程序)
             + p(path,参数若含“/“则视为路径,否则视为不带路径的程序名)
        例如:

            execlp("ps","ps","-ef",NULL);//第一个ps是程序名,第二个ps是第一个命令行参数
            perror("exec ps");//execlp执行出错时才会执行
            exit(1);

            
    3、退出进程
        一个C语言的程序总是从main()函数开始执行
        main函数原型:

            int main(int argc, char * argv[])

            
                argc: 命令行参数的数目
                argv:指向参数的各个指针所构成的数组
            
        进程正常退出的三种方式:由main()函数返回,调用exit()函数,调用_exit()或_Exit()函数
        exit函数原型:

            void exit(int status)
            void _exit(int status)

            
            _exit():直接使进程停止运行,清除其使用的内存空间,并清除其在内核的各种数据结构
            exit():在执行退出之前加了若干道工序,检查文件的打开情况,把文件缓冲区中的内容写回文件
                (清理I/O缓冲)。
                /**若进程涉及对文件的操作,为了保证数据的完整性就一定要使用exit()函数**/
        例如:

            printf("output begin\n");//p1
            printf("content in buffer");//p2
            exit(0);//p1,p2都显示
            //_exit(0);//只显示p1

            
        //在一个进程调用exit之后,该进程并不会马上完全消失,留下一个称为僵尸(Zombie)的数据结构。
        
        Zombie进程:    
            如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称
        为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程。
        
    4、wait和waitpid
        如果一个父进程终止,而它的子进程还存在(仍在运行或成为僵尸进程),此时这些子进程的父进程将
    改为init进程。只要有子进程终止,init就会调用wait函数清理它。
    
    wait函数原型:

        pid_t wait(int * status)
        pid_t waitpid(pid_t pid, int * status, int options)

        
        status: 为空——代表任意状态结束的子进程;不为空——指定状态结束的子进程。
        pid: 设置等待进程,
            pid>0: 等待进程ID等于pid的子进程,不管其它子进程的状态,直至指定的子进程结束才停止等待
            pid=-1: 等待任意一个子进程退出
            pid=0: 等待其组ID等于调用进程的组ID的任一子进程函数传入值
            pid<-1: 等待其组ID等于pid的绝对值的任一子进程
        options:
            WNOHANG:若由pid指定的子进程不立即可用,则waitpid不阻塞,此时返回值为0
            WUNTRACED:若实现某支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来
                       还未报告过,则返回其状态函数传入值。
        返回值:调用成功——返回清理掉的子进程ID;没有子进程退出——返回0;出错——返回-1