当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改
这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝技术
创建进程的时候,如果直接分离拷贝的话,可能根本用不到,即使用到了,也可能只是读取,所以编译器器编译程序的时候不会直接实现分离拷贝
在我们用的时候再分配,这是一种高效使用内存的一种表现,但是OS是无法在代码执行前预知那些空间会被访问
1、为什么数据要进行写时拷贝?
2、为什么不在创建子进程的时候就进行数据的拷贝?
3、代码会不会进行写时拷贝?
父进程通过进程等待的方式,回收子进程,获取子进程的退出信息
#include
#include
#include
#include
#include
#include
#include
typedef void (*handler_t)(); //函数指针类型
std::vector handlers; //函数指针数组
void fun_one()
{
printf("这是一个临时任务1\n");
}
void fun_two()
{
printf("这是一个临时任务2\n");
}
// 在父进程以非阻塞方式等待时
// 只要向Load里面添加内容,就可以让父进程执行对应的方法喽!
void Load()
{
handlers.push_back(fun_one);
handlers.push_back(fun_two);
}
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程
int cnt = 5;
while(cnt)
{
printf("我是子进程: %d\n", cnt--);
sleep(1);
}
exit(11); // 11 仅仅用来测试
}
else
{
int quit = 0;
while(!quit)
{
int status = 0;
pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待
if(res > 0)
{
// 等待成功 && 子进程退出
// WEXITSTATUS(status) 等价于 (status >> 8) & 0xFF
printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));
quit = 1;
}
else if( res == 0 )
{
// 等待成功 && 但子进程并未退出
printf("子进程还在运行中,暂时还没有退出,父进程将执行其他任务\n");
if (handlers.empty())
Load();// 加载任务
std::vector::iterator iter = handlers.begin();
while (iter != handlers.end())
{
(*iter)();
iter++;
}
//for(auto iter : handlers)
//{
// // 执行处理其他任务
// iter();
//}
}
else
{
//等待失败
printf("wait失败!\n");
quit = 1;
}
sleep(1);
}
}
}
1. 父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid函数呢?直接使用全局变量不行吗?
2. 既然进程是具有独立性的,进程退出码,不也是子进程的数据吗,父进程怎么拿到的呢?wait和waitpid究竟做了什么?
fork()之后父子各自执行父进程代码的一部分,但如果子进程就想执行一个全新的程序呢,这时就可以通过进程的程序替换,来完成这个功能
当进行进程程序替换时,有没有创建新的进程?
子进程进行进程程序替换后,会影响父进程的代码和数据吗?
为什么要进程替换?
为什么我要创建子进程?
shell 运行原理:通过让子进程执行命令,父进程阻塞等待&&解析命令
#include
#include
#include
#include
#include
#include
#define NUM 1024
#define SIZE 32
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];// "ls -a -l -i"
//保存打散之后的命令行字符串
char* g_argv[SIZE];// "ls" "-a" "-l" "-i"
//
int main()
{
//0. 命令行解释器,一定是一个常驻内存的进程,不退出
while (1)
{
//1. 打印出提示信息 [whb@localhost myshell]#
printf("[root@localhost myshell]# ");
fflush(stdout);
memset(cmd_line, '\0', sizeof cmd_line);
//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
if (fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
{
continue;
}
cmd_line[strlen(cmd_line) - 1] = '\0';
//回车会触发\n "ls -a -l -i\n\0"
//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
int index = 1;
if (strcmp(g_argv[0], "ls") == 0)
{
g_argv[index++] = "--color=auto";
}
if (strcmp(g_argv[0], "ll") == 0)
{
g_argv[0] = "ls";
g_argv[index++] = "-l";
g_argv[index++] = "--color=auto";
}
while (g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL
//4. TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
//内建命令本质其实就是shell中的一个函数调用
if (strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
{
if (g_argv[1] != NULL) {
chdir(g_argv[1]); //cd path, cd ..
}
continue;
}
//5. fork()创建父子进程
pid_t id = fork();
if (id == 0) //child
{
printf("下面功能让子进程进行的\n");
//cd cmd , current child path
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0) {
printf("exit code: %d\n", WEXITSTATUS(status));
}
}
}