进程控制

进程创建

      fork()

               1.以父进程为模板赋值创建一个人子进程,父子进程代码共享,数据独有。(写时拷贝技术)

               2.fork的返回值,父进程返回子进程的pid,子进程返回0

               3.父子进程谁先执行是不一定的,这取决于CPU的调度。

      vfork()

              1.创建一个进程,(它与fork的区别是vfork()创建的子进程的虚拟地址空间是相同的,这样子进程改变数据的话,父进程中的数据也会改变)。
              2.对于vfork()而言,只要子进程不运行其他的程序或者不退出的话(因为vfork()的特性是子进程先运行),父进程是一直等待的。(因为创建出的子进程大多时候是为了让他运行其他的程序)。(子进程退出的时不能再main函数中用return退出,因为return的话,会释放所有的资源,因为父子进程共用的是同一块地址空间,所以如果子进程释放了所有的资源后,父进程再次访问的时候就会出错,退出的时候可以用exit来退出)。
              3.父进程为什么先不运行的原因:

                        因为父子进程公用同一块虚拟的空间。他们公用同一块栈区,有可能会造成调用栈的混乱.

             vfork设计出来的目的是为了创建一个子进程,然后直接运行其他的程序。重新运行其他的程序就是重新给子进程开辟新的空间,更新它自己的一份地址空间和页表(这样就不会和父进程发生冲突)。自从fork函数使用了写时拷贝技术之后,vfork就被淘汰了。

下面是一个vfork的演示:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
        pid_t pid = vfork();
        if(pid == 0)
        {
                sleep(5);//这是为了证明vfork的特性是让子进程先执行(如果这样做的话,无法证明子进程和父进程谁先执
行)
                printf("this is child\n");
                exit(0);
        }
        else if(pid > 0)
        {
                printf("this is parent\n");
        }
        return 0;
}

进程的中止

        进程退出的几种方式:
                1.exit(int status) (staus是进程的退出原因)       退出进程(退出的时候会逐步释放所有的资源)。在进程的任意位置调用进程时,都会退出进程。退出前会刷新缓冲区,关闭文件,做很多操作
                2._exit(int status)(系统的调用接口,exit就是调用这个接口的退出的):直接退出,不会刷新缓冲区。直接释放所有的资源,粗暴的退出,什么也不做。
        exit和_exit的区别:
                        exit是温和型的退出,温和的释放所有的资源,刷新缓冲区。
                        _exit是暴力退出,直接释放资源,不会刷新缓冲区。
                换行符:刷新缓冲区,让我们打印的文件即使的出现在显示器上。

#include<stdio.h>
#include<stdlib.h>

int main()
{
    printf("hello word");
    exit(0);
    //_exit(0);
    return 0;
}

上面这两种退出,exit执行的退出最后会打印hello word,而_exit执行的时候什么都不会打印,因为_exit退出时不会刷新缓冲器,所以不会将缓冲区的数据打印到显示器上。
               3.return ;只有在main函数中执行才会退出进程,他和调用exit的效果一样,因为调用main运行时;函数会将main的返回值当作exit的参数。

        不管哪种退出方式,都会返回一个数字,这个数字是进程的退出状态,它表明了退出的原因。
                1.正常运行完毕之后,结果符合预期。
                2.正常运行完毕之后,结果不符合预期。
                3.异常退出,返回状态将不能做为评判的标准。

进程等待

        一个进程退出之后,因为要保存退出的原因,因此并不会释放所有的资源,它等着父进程查看它的退出原因,然后释放所有的资源。如果父进程不管的话,这个进程就变成了僵尸进程,这个僵尸进程的危害就是造成资源泄露。因此为了防止出现僵尸进程,父进程就会管一下这个子进程。

       进程的等待就是等子进程的状态改变,获取子进程的退出状态码,允许操作系统释放进程的所有资源,这时子进程的所有资源才会被释放掉。进程等待是避免僵尸进程的主要方式。

进程等待的方式:

             1.pid_t wait(int*status) status用于获取退出状态码,返回值是返回退出的子进程pid
                         wait:等待任意一个子进程的退出(他是一个阻塞式的调用,如果没有子进程的退出的话,就一直等待,不返回,直到子进程退出)。如果没有子进程你使用wait的话,会报错,wait的返回值为-1,它会直接返回,不会继续等待子进程的退出。(因为系统直到你有没有子进程)。
            pid _t wait(int*status),status保存的子进程的退出状态,如果你不需要保存退出状态的话,变成NULL即可

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<error.h>

int main()
{
        pid_t pid = fork();
        if(pid < 0)
                exit(-1);
        else if(pid == 0)
        {
                sleep(2);
                exit(0);
        }
        pid_t id = -1;
        if((id = wait(NULL)) < 0)
        {
                    perror("wait error");
        }
        printf("child : %d   wait: %d\n",pid, id);
        return 0;
}

            2.waitpid(pid_t pid, int *status, intoptions);等待一个指定的ID(它是一个阻塞/非阻塞的可选函数)

                        pid = -1,代表等待任意一个子进程(和wait的功能一样)。

                        pid >0,代表等待一个指定的子进程

                        status和上面的一样,都是保存退出的状态码。(虽然用了四个字节来存储信息,但是真正存储信息的只有两个字节,也就是后16个比特位)
                        status也可以当作一个位图看待:
                                在status的低16位中,前8位是保存退出的状态,只有在程序退出后才会有(如果一个进程异常退出的话,没有退出的状态,程序的退出状态码为0,但是也有可能退出状态本身就是0),后8位表明了这个进程是因为什么异常退出的,如果这个进程是正常退出的话,后8位为0(也就是导致退出的信号值)(查看所有的信号 kill -l).WIFEXITED(status)代码运行完毕之后退出。

                               WIFSIGNNALED(status)异常信号导致的退出,WTERMSIG(status)查看到底是什么信号。

                options 代表的是选项参数:
                        WNOHANG:非阻塞,如果有子进程的话,等待子进程的退出,如果没有子进程的话,直接退出。
                        0:是阻塞。

                waitpid的返回值:

                         -1:代表出错。
                          0:代表没有子进程退出。
                        >0:代表退出的子进程的pid.
                wait阻塞:没有子进程退出的话就一直等待,wait等待任意一个子进程,而不是等待所有。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<error.h>

int main()
{
        pid_t pid = fork();
        if(pid < 0)
                exit(-1);
        else if(pid == 0)
        {
                sleep(2);
                exit(0);
        }
        pid_t id = -1;
        while((id = waitpid(pid,NULL,WNOHANG)) == 0)//循环的查看有没有子进程退出
        {
                ;
        }
        printf("child : %d   waitpid: %d\n",pid, id);
        return 0;
}

                获取退出的状态码:
                        1.WIFEXITED通过wait获取的状态判断进程是否正常退出。
                        2.WEXITSTATUS正常退出则获取一下进程退出时的返回的状态码(虽然退出状态用了4个字节获取,但是实际值用了低16位的两个字节存储有用的信息)。
                                 高8位存储的时进程退出时返回的状态码(程序运行完毕之后才会有)。(如果一个进程异常退出的话,就没有状态码)
                                 低7位存储的时引起进程异常退出的信号。(如果一个进程正常退出的话,也就没有异常退出的信号了)
                                 还有中间一位时core dump(核心转储)标志。

进程的程序替换

        1.程序替换替换的是代码段所指向的物理内存区域,相当于让虚拟地址指向来了物理内存的另一端代码位置
        2.这样的话,虚拟地址空间中原来的数据区域以及堆栈都会重新初始化,因为现在的代码运行的根本不是原来的数据
        3.这个进程的PCB还是原来的PCB。

        4.一个程序main函数中有三个参数,一个是参数的个数,一个是字符串指针,一个是环境变量。
        5.exec函数组:

首先看一下exec函数组的定义;

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

              exec函数组的作用是程序替换,如果替换成功的话,代表运行的代码段已经不是原来的代码段了,而是一个新的程序。因此exec之下的代码都不会再运行了,除非exec出错了,也就是下面的程序基本没有运行的可能。
             execl和execlp的区别
                     execl     需要告诉操作系统这个文件的全路径

execl("/bin/ls","ls","-l".NULL);

                    execlp   不需要告诉路径,只需要告诉文件名即可,操作系统会自动的到PATH中的路径下寻找

execlp("ls","ls", "-l",NULL);

           execl与execle的区别:

                execl:      继承于父进程的环境变量

                execle     由我们用户自己来组织环境变量

char const *envp[] = {"PATH=/bin:/usr/bin","TERM=console",NULL};
execle("ls", "ls", "-l", NULL, envp);

          execl和execv的区别

                execl: 将路径后面的选项全部写出

execl("/bin/ls","ls","-l".NULL);

                execv:将路径后面的内容放入数组中,直接传入数组即可

char *const argv[] = {"ls", "-l", NULL};
execv("/bin/ls", argv);

                execv和execve 、execv和execvp的区别和上面的区别类似。 

                exec函数如果执行成功的话,exec函数下面的所有语句都不会再执行了,因为他们全部被替换了,现在指向的是新程序的一块新的物理内存空间。

                exec函数只有出错的时候才会有返回值,而成功是没有返回值的。如果调用出错的话就返回-1.
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值