进程二(进程的消亡以及释放资源,exec函数族的使用)

进程的消亡以及释放资源

wait();//相当于 waitpid(-1, &status, 0);
waitpid();
waitid();
-------wait for process to change state

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

   pid_t wait(int *status);//死等,是一个阻塞态
   pid_t waitpid(pid_t pid, int *status, int options);//通过options设置等待方式

如果options为0那么就相当于wait,如果为WNOHANG,那么就会让阻塞态变成非阻塞态
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

返回值:
wait(): on success, returns the process ID of the terminated child; on
error, -1 is returned.

   If status is not NULL, wait() and waitpid() store status information in
   the int to which it points.  This integer can  be  inspected  with  the
   following  macros  (which take the integer itself as an argument, not a
   pointer to it, as is done in wait() and waitpid()!):

status介绍
WIFEXITED(status)//是否是正常结束
returns true if the child terminated normally, that is, by call‐
ing exit(3) or _exit(2), or by returning from main().

WEXITSTATUS(status)//进程结束的状态
returns the exit status of the child. This consists of the
least significant 8 bits of the status argument that the child
specified in a call to exit(3) or _exit(2) or as the argument
for a return statement in main(). This macro should be employed
only if WIFEXITED returned true.//只有这个WIFEXITED宏的返回值为真,才可以使用WEXITSTATUS,返回退出码是多少
WIFSIGNALED(status)//如果当前子进程是由一个信号终止的,那么这个宏的检测回为真
returns true if the child process was terminated by a signal.

WTERMSIG(status)//把导致当前进程结束的signal number返回
returns the number of the signal that caused the child process
to terminate. This macro should be employed only if WIFSIGNALED
returned true.
…(其他的一些宏可自行查看man手册)

options介绍
The child state changes to wait for are specified by ORing one or more
of the following flags in options:

   *WEXITED*    Wait for children that have terminated.

   **WSTOPPED**    Wait for children that have been stopped by delivery  of  a
               signal.

   **WCONTINUED**  Wait  for  (previously  stopped)  children  that  have been
               resumed by delivery of SIGCONT.

   The following flags may additionally be ORed in options:

   **WNOHANG**     As for waitpid().

   **WNOWAIT**     Leave the child in a waitable state; a later wait call  can
               be used to again retrieve the child status information.

pid介绍
The value of pid can be:

   < -1   meaning wait for any child process whose  process  group  ID  is
             equal to the absolute value of pid.

   -1      meaning wait for any child process.

   0       meaning  wait  for  any  child process whose process group ID is
            equal to that of the calling process.

   > 0   meaning wait for the child whose process  ID  is  equal  to  the
            value of pid.

程序加上wait的使用

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

#define LEFT 30000000
#define RIGHT 30000200

int main()
{    
    pid_t pid;
    int i,j,mark;
    for(i=LEFT;i<=RIGHT;i++)
    {    
        pid=fork();
        if(pid<0)
        {    
            perror("fork()");
            exit(1);
        }
        if(pid==0)
        {    
            mark =1;
            for(j=2;j<i/2;j++)
            {
                if(i%j==0)
                {
                    mark=0;
                    break;    
                }
            }
            if(mark)
            {
                printf("%d is a primer\n",i);
            }
//            sleep(1000);
            exit(0);
        }
    }
//    int st;
    for(i=LEFT;i<=RIGHT;i++)
    {

    //    wait(&st);//care the status关心收尸的状态
        wait(NULL);//don not care the status    不关心
    }
    exit(0);

}

将201个进程改为N个进程来算质数
思路一:
分块法,假设当前N的值为3,则将数据平均分为3份,这里就是将201个质数分为3份
在这里插入图片描述

这三个进程任务的分配是不平均的
那么这三个进程的那个负载最重?
进程一负载最重,因为质数在小的数里分布多点

思路二:
交叉分配,有了一定的随机性,第一个数交给进程一,第二个数交给进程二,第三个数交给进程三,第四个数交给进程一·········
在这里插入图片描述

交叉分配,在这个模型中,不好用,会有某个进程拿到的是3的倍数,出现一个质数都拿不到的情况,但是在实际工程中,交叉分配和分块法,我们会优先选择交叉分配法

思路三:
池类算法:能者多劳,分配具有随机性
在这里插入图片描述

交叉分配法的实现:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

#define LEFT 30000000
#define RIGHT 30000200
#define N 3
int main()
{    
    pid_t pid;
    int i,j,mark,n;
for(n=0;n<N;n++)
{
    pid=fork();
        if(pid<0)
        {
           perror("fork()");
           exit(1);
        }

    if(pid==0)
    {
        for(i=LEFT+n;i<=RIGHT;i+=N)
        {        
            mark =1;
            for(j=2;j<i/2;j++)
            {
                if(i%j==0)
                {
                    mark=0;
                    break;    
                }
            }
            if(mark)
            {
                printf("[%d]%d is a primer\n",n,i);
            }
        }
        exit(0);
    }
}

    for(n=0;n<N;n++)
    {
        wait(NULL);    
    }
    exit(0);

}

运行结果:

在这里插入图片描述

exec函数族的使用

在这里插入图片描述

我们查看进程关系的时候会看到这个样的情况

bash产生了./primer
./primer1又产生了201个一摸一样的子进程

那么为什么bash产生的是./primer1
而不是一个一摸一样的bash????

相关函数:
execl();
execlp();
execle();
execv();
execvp();
-------execute a file

    #include <unistd.h>

   extern char **environ;

   int execl(const char *path, const char *arg, ...
                   /* (char  *) NULL */);
   int execlp(const char *file, const char *arg, ... 
                   /* (char  *) NULL */);
   int execle(const char *path, const char *arg, ...
                   /*, (char *) NULL, char * const envp[] */);
           //以上三个函数是定参的实现,参数以NULL表示结束
   int execv(const char *path, char *const argv[]);
   int execvp(const char *file, char *const argv[]);
           //上面两个是变参的实现

描述:
The exec() family of functions replaces the current process image with
a new process image.

返回值:
The exec() functions return only if an error has occurred. The return
value is -1, and errno is set to indicate the error.
用新的进程映像替换新的进程映像(你还是你,你已经不是你了,就是壳子还在,但是内容变了,这里的壳子指的是pid不变)
例子:

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


/*

date +%s

*/

int main()
{

        puts("Begin!");

        execl("/bin/date","date","+%s",NULL);
        perror("execl()");//如果execl成功了,就不会回来,已经用新的进程映像把旧的替换掉了,如果回来的话就一定出错了,就报错
        exit(1);

        puts("End!");
        exit(0);
}

运行结果:
在这里插入图片描述

就说明变成功了,变成了date +%s来执行

如果上面的程序,重定向到文件中去,会有不同的结果,看不到Begin!

在这里插入图片描述

注意:在exec一族函数的使用之前要注意fflush的使用

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


/*

date +%s

*/

int main()
{

        puts("Begin!");
        fflush(NULL);
        execl("/bin/date","date","+%s",NULL);
        perror("execl()");
        exit(1);


        puts("End!");
        exit(0);
}

运行结果:
在这里插入图片描述

上面的程序意义不大。弄一个程序变成另一个,还不如一开始就是另一个

例子:fork+exec+wait

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t pid;
    puts("Begin!");

    fflush(NULL);
    pid=fork();
    if(pid<0)
    {
        perror("fork()");
        exit(1);
    }

    if(pid==0)
    {
        execl("/bin/date","date","+%s",NULL);
        perror("execl()");
        exit(1);
    }

    wait(NULL);
    puts("End!");
    exit(0);
}

运行结果:
在这里插入图片描述

描述:
Begin!父进程打印的
然后fork出子进程,子进程变成了date +%s
然后父进程等着收尸,等子进程完成工作后。收完尸打印End!
最后结束

看一下在终端输入命令ls后是怎么工作的
ls命令在shell环境下执行的时候,shell做了什么样的工作
在这里插入图片描述

shell在执行一个命令的时候,其实就是fork产生一个子进程。产生的子进程就是shell本身
然后shell再去exec让子进程变成ls,在子进程运行的时候,父进程在执行wait,所以我们在终端看到的效果都是结果先显示出来,然后命令行再弹出来。就是等子进程执行完以后父进程把子进程收尸,父进程就会去干别的事,由于shell是一个死循环,所以shell会继续打印出命令行等待从终端输入情况
在这里插入图片描述

上图解释了父子进程同用一个terminal的原因

利用few代码实现让子进程sleep(100);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
        pid_t pid;
        puts("Begin!");

        fflush(NULL);
        pid=fork();
        if(pid<0)
        {
                perror("fork()");
                exit(1);
        }

        if(pid==0)
        {
                execl("/bin/sleep","sleep","100",NULL);
                perror("execl()");
                exit(1);
        }

        wait(NULL);
        puts("End!");
        exit(0);
}

在这里插入图片描述
请添加图片描述
在这里插入图片描述

这里的100也是可以封装起来的
木马的低级的藏身办法,可以使用这种方法实现
比较高级的隐藏在内核模块当中

写出一个简单的shell
先确定shell是一个死循环,首先命令行打印出来,然后有一个 符 号 , 符号, 后我们输入命令
shell环境下有两种命令一个外部命令一个是内部命令

相关函数:
strtok()
----------------- extract tokens from strings

    #include <string.h>
   char *strtok(char *str, const char *delim);

strsep()
-------------- extract token from string

   #include <string.h>
   char *strsep(char **stringp, const char *delim);//根据分割符将一个串分为几个部分

代码使用strsep函数

例子:shell的外部命令处理

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>

#define DELIMS " \t\n"

struct cmd_st
{

    glob_t globres;

};

static void prompt(void)
{
    printf("myshell-0.1$ ");
}


static void parse(char *line,struct cmd_st *res)
{
    char *tok;
    int i=0;
    while(1)
    {
        tok=strsep(&line,DELIMS);
        if(tok==NULL)
        {    
            break;
        }
        if(tok[0]=='\0')
        {
            continue;
        }

        glob(tok,GLOB_NOCHECK|GLOB_APPEND*i,NULL,&res->globres);
        i=1;
        
            

    }
}


int main()
{
    pid_t pid;
    char *linebuf=NULL;
    size_t linebuf_size=0;
    struct cmd_st cmd;
    while(1)
    {
        prompt();
        
        if(getline(&linebuf,&linebuf_size,stdin)<0)
        {
            break;
        }

        parse(linebuf,&cmd);

        if(0)
            {
            //do something
            }
               else
            {
            pid=fork();
            if(pid<0)    //error
            {
                perror("fork()");
                exit(1);
            }

            if(pid==0)    //child
            {
            
                execvp(cmd.globres.gl_pathv[0],cmd.globres.gl_pathv);
                perror("execvp()");
                exit(1);

            }
            else    //parent
            {
                wait(NULL);
            }
            
            }

    }
    
    exit(0);
}

运行结果:对于外部命令是可以实现的
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值