进程的消亡以及释放资源
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);
}
运行结果:对于外部命令是可以实现的