创建进程
fork和vfork函数。
二话不说 上栗子。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int gvar=2;
int main(void)
{
pid_t pid;
int var=5;
printf("process id:%ld\n",(long)getpid());
if((pid=vfork())<0)
{
perror("error!");
return 1;
}
else if(pid==0)
{
gvar--;
var++;
printf("the child process id:%ld\ngvar=%dvar=%d\n",(long)getpid(),gvar,var);
_exit(0);
}
else
{
printf("the parent process id:%ld\ngvar=%d var=%d\n",(long)getpid(),gvar,var);
return 0;
}
}
这里用vfork创建了一个进程,pid==0的部分是子进程的部分。创建完进程之后出现了分叉。调用一次返回2次。下面开一下输出结果
root@kali:~/桌面# gcc vfork_fun.c -o vfork_fun
root@kali:~/桌面# ./vfork_fun
process id:8026
the child process id:8027
gvar=1var=6
the parent process id:8026
gvar=1 var=6
这里只在子进程里面修改了2个变量的值,但是父进程中2个变量的值同样改了
下面看一下fork函数
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int gvar=2;
int main(void)
{
pid_t pid;
int var=5;
printf("process id:%ld\n",(long)getpid());
if((pid=fork())<0)
{
perror("error!");
return 1;
}
else if(pid==0)
{
gvar--;
var++;
printf("the child process id:%ld\ngvar=%dvar=%d\n",(long)getpid(),gvar,var);
_exit(0);
}
else
{
printf("the parent process id:%ld\ngvar=%d var=%d\n",(long)getpid(),gvar,var);
return 0;
}
}
这里只更改了一下创建进程函数的调用。
这个2个父进程的临时变量是不会更改的。
exec()函数簇
大多数情况下我们是希望新进程实现不同功能的。而exec()函数并没有创建新进程,而是把原来进程的代码段,数据段,堆栈段都替换掉。下面将以execve()函数为例(其他函数在调用时最终也都会调用execve函数)
execve函数会清除掉原来的函数运行信息,然后将一份新的环境变量表传递给它。
execve.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
extern char **environ;
int main(int argc,char * argv[])
{
execve("new",argv,environ);
puts("hello!");
}
new.c
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(void)
{
puts("welcome to mrsoft!");
return 0;
}
编译后执行的结果如下
root@kali:~/桌面# gcc execve.c -o execve
root@kali:~/桌面# gcc new.c -o new
root@kali:~/桌面# ./execve
welcome to mrsoft!
程序并没有执行puts(“hello!”); 而是执行了puts(“welcome to mrsoft!”);。
是不是觉得很神奇,其实过程中只不过是删除了原本的程序运行信息,在本进程内加载了new程序的运行信息。
下面用execlp()函数来举个例子。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char* argv[])
{
if(argc<2)
{
printf("vi de deng xiao yong fa: %s filename\n",argv[0]);
return 1;
}
execlp("/usr/bin/vi","vi",argv[1],(char*)NULL);
return 0;
}
有些系统vi可能是在/bin/vi下。
这里可以总结一下execlp的用法:第一个参数:路径。第二个参数:文件名。第三个参数是参数数组,可变,并以char* NULL结尾
顺便补充一下,argc表示参数列表的数目。argv*表示参数表。argv[0]是该文件的名字,argv[1]是输入的第一个命令行参数。还有一点就是argc表示的数目连argv[0]也算进去了。
wait函数与僵尸进程
进程等待是为了让父进程等待子进程结束如果没有调用等待函数子进程就会编程僵尸进程。
wait函数的工作过程:
- 判断子进程是否存在
- 不存在子进程提示错误信息ECHILD,并返回
- 存在子进程则将父进程挂起直到子进程结束,并返回结束时状态和最后结束的子进程的PID。
如果存在子进程,退出时结束状态(status)有如下2种可能。
当调用wait函数紫禁城结束后函数会返回进程的PID和status状态此时参数status所指向的状态变量就存放在子进程的退出码中
wait函数发信号引起子进程,若发送的信号被子进程捕获子进程就会终止。若信号没有被子进程捕获会使子进程非正常结束,此时参数status返回的状态值为接收到的信号值,存放在最后一个字节中。
关于这2种退出的情况具体看下面例子
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
void exit_s(int status)
{
if(WIFEXITED(status))
printf("normal exit,status=%d\n",WEXITSTATUS(status));
else if(WIFSIGNALED(status))
printf("signal exit!status=%d\n",WTERMSIG(status));
}
int main(void)
{
pid_t pid,pid1;
int status;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid==0)
{
printf("the child process!\n");
exit(2);
}
if(wait(&status)!=pid)
{
printf("this is parent process!\nwait error!\n");
exit(0);
}
exit_s(status);
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid==0)
{
printf("the child process!\n");
pid1=getpid();
kill(pid1,9);
}
if(wait(&status)!=pid)
{
printf("this is a parent process!\nwait error!\n");
exit(0);
}
exit_s(status);
exit(0);
}
下面是kill常用的信号
HUP 1 终端断线
INT 2 中断(同 Ctrl + C)
QUIT 3 退出(同 Ctrl + \)
TERM 15 终止
KILL 9 强制终止
CONT 18 继续(与STOP相反, fg/bg命令)
STOP 19 暂停(同 Ctrl + Z)
输出结果为
root@kali:~/桌面# gcc wait.c -o wait
root@kali:~/桌面# ./wait
the child process!
normal exit,status=2
the child process!
signal exit!status=9
WIFEXCITED (status):当子进程正常退出时返回真值。WEXCITSTATUS(status)返回子进程正常退出时的状态(也就是输出status里面保存的之前运行进程的返回值)。
WIFSIGNALED(status)当子进程被没有捕获的信号停止时返回真值。WTERMSIG(status)用于子进程被信号终止的情况返回此信号的类型。
进程结束
前面用vfork创建的子进程退出时只能用_exit()而不能用exit()。使用exit()函数会对输入输出流进行刷新释放占用的资源以及清空缓冲区等。而_exit()不具备此功能。