进程控制:

1、 进程创建函数: fork();

       头文件:

       #include<sys/types.h>

       #include<unistd.h>

      

       函数原型:

       pid_t fork(void);


        函数返回值:0:表示此进程现在是子进程;

                                -1:表示出错;

                                子进程ID号;(大于零的整数):表示现在此进程时父进程,接收到的ID号是子进程的ID号;


2、fork()返回-1(也就是进程创建出错的原因)

      1、系统中拥有了太多的进程;超过了系统的限制;(系统级)

      2、该用户有了太多的进程,超过了CHILD_MAX的限制;(用户级);


3、fork()的用途;

      1、父进程希望赋值自己的代码,使父子进程不同的代码段;如:网络服务器的经典代码;

      2、一个进程要执行一个完全不同的程序;从fork()返回后,立即调用exec()函数,去执行另外的程序;


4、子进程可用通过getpid 获取自己的ID号,也可以通过getppid()获取父进程的ID号;

      但是父进程只可以通过getpid获取自己的ID号,不能通过其他函数获取子进程的ID号;唯一获取子进程ID号的机会就是在父进程调用fork()时,获取其返回值,该值就是子进程的ID号;


5、进程内容切换:exec()函数簇;

      头文件 : 

      #include <unistd.h>


     函数:参数由三部分组成:即路径,传递给进程的参数,环境变量;

     int execl(const char* path, const char* arg,...);

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

     int execle(const char* path, const char* arg1,....,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[]); 


     注意:除了exec这几个字符外,

                函数中含有“l”的表示传递给进程的参数 将由列表形式展出;记住,最后一个参数是NULL,作为哨兵,表示参数列表完毕;传递给进程的第一个参数,相当于main中的argv[0];

                函数中含有“v”,表示传递的参数将以指针数组的形式展出;传递给进程的第一个参数,相当于main中的argv[0];

                函数中含有“p”,表示不用指定路径,系统将会去环境变量指定的路径中区寻找;不含p的表示一定要指明文件所在的路径;

                函数中含有“e”,表示需要用户指定环境变量,不含p的表示该进程继续沿用父进程的环境变量;


    只有在出错时,才会有返回值;

    出错的几种可能的原因:

    第一,路径不正确;

    第二,文件不存在;

    第三,权限不够;

    第四,不知道;

    调试要使用perror(“函数名:“)打印调试信息;



6、system(char * string)函数;

      作用:执行一条命令“string”;

      system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令;

在该command执行期间,SIGCHLD是被阻塞的,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。

       ,实际上system()函数执行了三步操作:

         1.fork一个子进程;

         2.在子进程中调用exec函数去执行command;

         3.在父进程中调用wait去等待子进程结束。

            对于fork失败,system()函数返回-1。

 

       源码:

      int system(const char * cmdstring)

{

    pid_t pid;

    int status;

if(cmdstring == NULL)

{

    return (1); //如果cmdstring为空,返回非零值,一般为1

}

if((pid = fork())<0)

{

    status = -1; //fork失败,返回-1

}

else if(pid == 0)

{

    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);

    _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~

}

else //父进程

{

    while(waitpid(pid, &status, 0) < 0)

    {

        if(errno != EINTR)

        {

            status = -1; //如果waitpid被信号中断,则返回-1

            break;

        }

    }

}

    return status; //如果waitpid成功,则返回子进程的返回状态

}



7、使用system()函数的建议

      system()函数有时很方便,但不可滥用!

1)、建议system()函数只用来执行shell命令,因为一般来讲,system()返回值不是0就说明出错了;

2)、建议监控一下system()函数的执行完毕后的errno值,争取出错时给出更多有用信息;

3)、建议考虑一下system()函数的替代函数popen();


8、进程终止:

      进程在终止时,会自动释放在“用户空间”分配的内存,但是PCB还保存着,保存着退出的状态,父进程可以使用wait() 或者waitpid()函数来获取子进程PCB中的信息,然后彻底清除这个子进程;


       僵尸进程:父进程还没调用wait() 或者 waitpid()来对已经终止的子进程进行清理工作时,此时子进程的 状态称为僵尸状态;正常情况下,僵尸进程都会被父进程立刻清理掉;

      kill 只能终止进程,并不能清理僵尸进程;


       如果一个父进程终止,而其子进程未终止,则子进程的父进程会改变为init进程;


       init进程时系统中的一个特殊进程,通常位于/sbin/init,进程ID = 1,该进程在启动时负责各种系统服务的启动,之后就负责清理子进程,只要有子进程终止,init就会调用wait()函数清理;


      


9、进程终止的方式:

      正常终止的方式:

      1)、void exit(int status);

      2)、void _exit(int status);


      异常终止的方式:

      1)、调用了abort;

      2)、由一个信号终止;


10、exit(int status)和_exit(int status)的区别:

        exit()先执行一些清除工作,如调用各终止处理程序,把文件缓冲区的内容写回文件,关闭所有标准I/O流,;然后再进入内核;头文件是 stdlib.h;

        _exit()则立即进入内核;使用的头文件是 unistd.h;直接是进程停止运行,清除内存空间,销毁内核中的各种数据结构,包括未被写入到文件的缓冲区内容也会被清理掉; 


        _exit(int status)是底层的系统调用,exit(int status)在底层调用了_exit(int status);

        status 用于传递进程结束时的状态;一般来说,0表示正常结束,其他结果都表示非正常结束;使用wait()函数可以接收子进程的返回值,然后根据返回值,进行不同的处理;



11、int atexit(void(* fun)(void)); 

       在调用exit(注意,不是_exit())时,进行清理工作以前,调用各种处理程序,而这些处理函数需要在使用atexit(void(*void)(void))函数进行注册,一个进程最多可以注册32个这样的函数,一旦注册成功,则在系统调用exit()时,会自动执行的这些函数,无需用户干预,值得注意的是,exit()调用的处理函数的执行:顺序与这些函数的注册顺序恰好相反;


12、wait 和waitpid的区别:

        pid_t wait(int *status):

               如果status 为空,则表示接受任意状态结束的进程,如果status 不为空,则表示接受指定状态结束的进程; 

                

               等待任意进程终止,status 用于保存子进程的终止状态;如果处理成功,则返回子进程的PID号;

                如果其他子进程都已经结束,或者没有子进程,wait都会立即返回;

                

       pid_t waitpid(pid_t pid, init *status, int options);

               pid>0时,表示只等待某个特定的进程结束;

               pid=0时,表示等待?????????????????

               pid=-1时,表示等待任何一个子进程结束,作用和wait()一样;

               pid<-1时,表示?????????????????        

        

               status 同wait();


               options: 当其值为:WNOHANG时,如果指定的PID的进程还未终止时,不会阻塞等待,而是立即返回,返回值是0;

                                当其值是:WUNTRACED时,???????????????



13、调用wait 和waitpid时可能出现的情况:

         1、阻塞:(如果其他子进程还在运行状态,则会进入阻塞状态进行等待;)

         2、带有子进程的终止信息返回(如果一个进程已经终止,正在等待父进程对其终止状态信息进行读取)

         3、出错立即返回-1;(如果他没有任何子进程);

                


14、wait()只是waitpid的一个特例 ;


15、使用wait() 或者 waitpid()可以实现进程之间的同步关系;


16、wait() 和waitpid()参数中的int* status 中包含了多个字段,用宏定义可以提取其中的每个字段;

        如果子进程是正常终止的,则WIFEXITED的字段不为0,WEXITSTATUS的值就是其进程退出的状态;

        如果子进程是异常终止的,则WIFSIGNALED取值字段不为0,WTERMSIG取出的字段值就是信号编号;


17、守护进程:(Daemon进程)

         是linux中的后台服务进程,独立于控制终端,并且周期性的执行某种任务,或者等待处理某些发生的事件;

         在系统引导装入时启动,在系统关闭时终止;