1.进程创建
fork()函数为什么要给子进程返回0,给父进程返回子进程的pid
父子进程立场:父进程不需要标识,子进程需要标识,因为父进程可以有多个子进程,而子进程只有一个父进程。子进程是要执行任务的,父进程需要通过子进程的pid来区分子进程,而子进程不需要
如何理解fork有两个返回值?
在fork()还没有return pid的时候,子进程就已经创建好了,return pid被执行了两次。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中(也就是fork()还没有返回子进程就已经被创建出来了)
- fork返回,开始调度器调度
fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
写时拷贝
? 通常,父子代码共享,父子不在写入时,数据也是共享的。当任意一方试图写入,便以写时拷贝的方式各自一份副本。这里所说的共享,是指父子进程对应的页表指向的是同一块物理内存。
为什么要写实拷贝呢?
2.进程终止
进程退出的场景有如下三种:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
为什么main函数要有返回值,返回值给了谁?
- 当你运行程序形成进程的时候,你是想让该进程完成某种任务,并且你需要知道任务完成的结果。main函数的返回值是进程退出码。0代表成功。!0代表失败,这是人为定义的。这个返回值是给操作系统。我们通过进程退出码来判断任务的完成情况。
我们可以使用echo $? 指令打印出最近一次进程退出时的退出码
如果退出码不是0,那么失败的原因有多种。每一种退出码都有对应的字符串含义,帮助用户确认,任务失败的原因。下面我们利用下面代码测试一下Linux下不同退出码代表的含义
#include <stdio.h>
2 #include <string.h>
3 int main()
4 {
5 int i = 0;
6 for(i = 0;i < 150; i++)
7 {
8 printf("%d: %s\n", i, strerror(i));
9 }
10 return 0;
11 }
运行结果如下,共134条错误码
进程退出的常见方法:
-
从main返回 只有main函数中的return代表进程退出 -
调用exit -
_exit 对于exit和_exit,在代码的任何地方,调用exit都代表进程退出!他们的差别是,exit会释放进程曾经占用的资源,比如缓冲区。而_exit直接终止进程,不会做任何的收尾工作! 为了更好地了解这一点,我们来用下面这段代码测试一下 #include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("hello world");
sleep(3);
_exit(0);
return 0;
}
这段代码如果使用exit,在进程终止之后会将缓冲区中的hello world打印到屏幕上,而_exit不会
进程如果是异常退出了,那么退出码将没有任何意义。
进程终止了,操作系统做了什么呢?
- 释放曾经申请的数据结构,释放曾经申请的内存,从各种队列等数据结构中移除
3.进程等待
4.进程程序替换
-
替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。 -
六种exec开头的替换函数 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 *path, char *const argv[], char *const envp[]);
l – list v – vector p:有p自己动搜索环境变量PATH e:表示自己维护环境变量
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
下面这段代码是对前5个函数的测试 其实前五个函数的底层都是第六个函数 #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("I am a process\n");
sleep(2);
char* myenv[] = {
"MYENV=HAHAHA",
NULL
};
execle("./mycmd", "mycmd", NULL, myenv);
exit(11);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("wait success\n");
printf("signal: %d\n", status & 0x7F);
printf("exit code: %d\n", WEXITSTATUS(status));
}
return 0;
}
5.简易shell
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#define LEN 1024
#define NUM 32
int main()
{
char* myarg[NUM];
char cmd[LEN];
while(1)
{
printf("[ls@VM-20-7-centos shell]$ ");
fgets(cmd, LEN, stdin);
cmd[strlen(cmd)-1] = '\0';
myarg[0] = strtok(cmd, " ");
int i = 1;
while(myarg[i] = strtok(NULL, " "))
{
i++;
}
pid_t id = fork();
if(id == 0)
{
execvp(myarg[0], myarg);
exit(11);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf("exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
|