1 进程创建
fork
创建进程成功,子进程返回0,父进程返回子进程的进程ID
在创建进程fork过程中,(1)分配新的内存块和内核数据结构给子进程
(2)子进程拷贝父进程的代码和数据
(3)子进程可以执行自己的代码
vfork
【注】:1 vfork也是创建进程,和fork类似
2 vfork创建的子进程和父进程共享一个地址空间(即:对于全局变量,子进程对其进行修改,父进程也改变了)
3 vfork保证子进程先运行,
子进程调用exec或_exit后
,父进程才开始运行。
通过一个小例子看一下vfork:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5
6 size_t a = 10;
7
8 int main()
9 {
10 //1 vfork保证子进程先运行,父进程后运行
11 //2 vfork创建的子进程和父进程共享地址空间
12 pid_t pid = vfork();
13 if(pid == 0)//child
14 {
15 printf("this is child,pid = %d,ppid = %d\n",getpid(),getppid());
16 a = 200;
17 printf("a = %d,&a = 0x%x\n",a,&a);
18 sleep(1);
19 _exit(0);
20 // exec
21 }
22 else if(pid > 0)//father
23 {
24 printf("this is father,pid = %d,ppid = %d\n",getpid(),getppid());
25 printf("a = %d,&a = 0x%x\n",a,&a);
26 sleep(5);
27 }
28 else
29 {
30 perror("vfork");
31 }
32 return 0;
33 }
我们可以看到,在子进程中更改a的值,父进程中a的值也发生改变。
进程退出的三种情况:(1)代码跑完了,结果正确;
(2)
代码
跑完了,结果不正确;
(3)
代码
没跑完。
进程的终止:三种正常终止:(1)在main函数调用return
(2)调用exit
(3)调用_exit
两种异常终止:(1)调用abort,产生SIGABORT信号,是异常终止的一个特例;
(2)进程接收到某个信号,如:Ctrl+c
【注】:exit在任何地方都可以表示进程终止,return只有在main函数中才表示进程终止。
_exit:参数status是进程的退出状态,没有返回值
exit:
exit和_exit的区别:
我们先通过一个例子感受一下他们的区别:
1 _exit是强制终止进程,简单粗暴
2 exit在终止进程之前,会执行用户定义的清理函数,刷新缓冲,关闭流等工作,最后再调用_exit终止进程。
3 进程等待
为什么要进程等待?
1 如果子进程退出,父进程没有得到子进程的退出信息,会造成子进程变为“僵尸进程”,
僵尸进程会浪费资源、内存泄漏,有很大的危害;
2 父进程通过等待的方式,回收子进程资源,获取子进程的退出信息;
3 进程等待可以保证父子进程的退出顺序。
进程等待的两种方式:1 阻塞式等待:“干等”就是在等待的时候,什么事情都不做
2 非阻塞式等待:需要等待,做其他事情,检测是否需要等待,需要等待,做其他事情......
wait和wait_pid:
wait:
参数:status 输出型参数,获取子进程的退出信息,如果不关心,可以设置为NULL
返回值:等待成功,返回子进程的进程ID,等待失败,返回-1
返回值:等待成功,返回子进程的进程ID,等待失败,返回-1
运行结果:
wait_pid:
参数:pid: pid = -1 等待任意一个进程,和wait等效
pid > 0 等待指定进程
status:输出型参数,获取子进程的退出信息,如果不关心,可以设置为NULL
options:默认为0:阻塞式等待
WNOHANG:非阻塞式等待
返回值:正常返回,返回子进程的进程ID;
设置WNOHANG选项,如果检测到子进程仍在运行,返回0;
等待失败:返回-1
wait_pid 以阻塞方式等待
运行结果:
wait_pid 以非阻塞方式等待
运行结果:
退出信息:status
通过上面的代码可以看到,我通过对status进行位运算,打印出了进程退出的信号和状态,
那么,status退出信息这个参数到底是什么样的呢?
status并不是一个简单的整型,可以当做位图来看它:
status并不是一个简单的整型,可以当做位图来看它:
4 进程替换
进程替换原理:在fork创建子进程之后,子进程往往通过进程替换,执行另一个程序,调用exec进程替换后,并没有创建新的进程,只是替换了代码和数据,并将栈和堆重新加载,所以其进程ID并没有改变。
六个替换函数:
六个替换函数:
#include <unistd.h>
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[]);
参数:path 可执行文件的目录
arg 替换的可执行文件,以NULL结束
file 文件名,会自动在PATH环境变量中找
envp 环境变量,以NULL结束(替换后不会继承原来的环境变量)
应用实例:
【注】:1 exec替换函数的参数只是做替换,不做参数的检验
2 六个exec函数中,execve是系统调用,其余五个函数为库函数
5 实现简易的myshell
思路:1 显示用户名、主机名、当前目录作为myshell输入的请示
2 从标准输入里面读取一行字符串
3 解析字符串,解析出指令和参数
(1)借助strtok字符创切分函数,以“ ”对字符串进程切分
(2)手动切分,遍历字符串,保存后面的字符创,将该处空格置成“\0”,
将切分出的该字符串保存在一个指针数组中
4 创建子进程,子进程程序替换,父进程等待
实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <pwd.h>
#include <sys/utsname.h>
#define FILEPATH_MAX (80)
//解析指令
void Analysis(char* str,char* argv[])
{
char* p = strtok(str," ");
int argc = 0;
while(p != NULL)
{
argv[argc++] = p;
//argv[argc] = p;
//printf("%s\n",argv[argc]);
//argc++;
p = strtok(NULL," ");
}
argv[argc] = NULL;
}
//进程替换
void Exec(char* argv[])
{
pid_t pid = fork();
if(pid == 0)//child
{
execvp(argv[0],argv);
//进程替换失败了,打印错误信息,并退出
perror("argv[0]");
exit(1);
}
else if(pid > 0)//father
{
wait(NULL);
}
else
perror("fork");
}
int main()
{
//获得用户名
struct passwd *pwd;
pwd = getpwuid(getuid());
//获得主机名
char computer[256];
gethostname(computer,256);
int i = 0;
while(computer[i] != '.')
{
i++;
}
computer[i] = 0;
//获得当前路径
char* file_path_getcwd;
file_path_getcwd = (char*)malloc(FILEPATH_MAX);
getcwd(file_path_getcwd,FILEPATH_MAX);
char* p = strtok(file_path_getcwd,"/");
int argc = 0;
char* argv[FILEPATH_MAX] = {p};
while(p != NULL)
{
argv[argc++] = p;
p = strtok(NULL,"/");
}
argv[argc] = NULL;
char buf[1024] = {0};
while(1)
{
printf("[%s@%s %s] ",pwd->pw_name,computer,argv[argc - 1]);
fflush(stdout);
gets(buf);
char* argv[10];
//解析输入的buf,解析出指令和参数
Analysis(buf,argv);
//fflush(stdout);
//进程替换
Exec(argv);
}
return 0;
}
运行: