甘肃兰州做网站,兰州网站排名哪家公司好,国外做电商平台的网站还有什么,网站怎么提升实用性目录 一、进程创建初识fork函数fork函数返回值写时拷贝fork常规用法fork调用失败的原因 二、进程终止进程退出场景进程常见退出方法_exit函数与exit函数 三、进程等待进程等待必要性进程等待的方法waitwaitpid 获取子进程status非阻塞等待测试 四、进程程序替换替换原理替换函数… 目录 一、进程创建初识fork函数fork函数返回值写时拷贝fork常规用法fork调用失败的原因 二、进程终止进程退出场景进程常见退出方法_exit函数与exit函数 三、进程等待进程等待必要性进程等待的方法waitwaitpid 获取子进程status非阻塞等待测试 四、进程程序替换替换原理替换函数函数解释命名理解 五、简易的shell 一、进程创建
初识fork函数
在Linux中fork函数时非常重要的函数它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。 返回值在子进程中返回0父进程返回子进程的PID子进程创建失败返回-1。
进程调用fork当控制转移到内核中的fork代码后内核做
分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork返回开始调度器调度
例子 运行结果 这里可以看到三行输出一行Before两行After。进程30363先打印Before消息然后它再打印After。另一个After消息由进程30364打印的。注意到进程30364没有打印Before为什么呢 因为Before是由父进程打印的而调用fork函数之后则是由父进程和子进程两个进程分别打印After。也就是说fork之前父进程独立执行fork之后父子两个执行流分别执行。 注意 fork之后父进程和子进程谁先执行完全由调度器决定。
fork函数返回值 子进程返回0 父进程返回的是子进程的pid。 那么为什么fork有两个返回值 因为在函数内部准备执行return的时候我们的主题功能就已经完成了也就是子进程就已经创建完毕了那么之后的父进程和子进程都执行了return所以就返回了两个值。
写时拷贝
在子进程刚刚创建的时候父子进程的代码是共享的父子在不写入时数据也是共享的只有当任意一方准备写入时便各自拷贝一份副本如下图所示 而这种按需申请资源的策略就是写时拷贝
为什么数据要写时拷贝 因为进程具有独立性。进程的之间的运行是互不影响的数据和代码是分开的代码是共用的而数据是各自用各自的不能让一个进程的修改影响到另一个进程所以就有了写时拷贝在需要修改数据的时候再分配这样便可以高效的使用内存空间。 运行结果 可以看到子进程对全局数据进行修改由于进程具有独立性独立性体现在数据层面在子进程对数据进行修改时进行了写时拷贝所以并不影响父进程。
fork常规用法
一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子 进程来处理请求。一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。
fork调用失败的原因
系统中有太多的进程实际用户的进程数超过了限制
二、进程终止
进程退出场景
进程退出只有三种情况
代码运行完毕结果正确代码运行完毕结果不正确代码异常终止进程崩溃
进程常见退出方法
进程退出都会有一个进程退出码我们一般以0表示代码正常执行完毕以非0表示代码执行过程中出现错误我们可以使用echo $?命令查看最近一次进程退出的退出码信息。 我们看看下面这个代码 我们可以看到main函数是正常执行完了。
我们也可以通过C语言中的strerror函数打印该错误码在C语言中所对应的错误信息如下
_exit函数与exit函数
使用exit函数退出进程也是我们常用的方法exit函数可以在代码的任意地方调用该函数都表示进程退出但在调用exit之前还做了其他工作
执行用户通过 atexit 或 on_exit 定义的清理函数。关闭所有打开的流所有的缓存数据均被写入调用_exit
执行exit(n)等同于执行return n, 因为调用main的运行时函数会将main的返回值当做 exit 的参数。 例如如下代码中exit函数终止进程前会将缓冲区当中的数据输出 但是_exit函数是直接干掉进程不会对缓冲区数据进行刷新。 如下
三、进程等待
进程等待必要性
子进程退出父进程如果不管不顾就可能造成‘僵尸进程’的问题进而造成内存泄漏。另外进程一旦变成僵尸状态那就刀枪不入kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。最后父进程派给子进程的任务完成的如何我们需要知道子进程运行完成结果对还是不对或者是否正常退出。父进程通过进程等待的方式回收子进程资源避免内存泄漏获取子进程退出信息
等待的本质就是通过系统调用获取子进程退出码或者退出信号的方式顺利释放内存问题。
进程等待的方法
wait pid_t wait(int* status); 返回值成功则返回被等待进程pid失败则返回-1。 参数输出型参数获取子进程退出状态,不关心则可以设置成为NULL。
如下父进程会等带子进程执行完毕
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.h
#include sys/types.h
int main()
{pid_t id fork();if (id 0){// childint count 3;while (count--){printf(I am child,PID:%d, PPID:%d\n, getpid(), getppid());sleep(1);}exit(0);}// fatherpid_t ret wait(NULL);if (ret 0){// wait successprintf(wait child success...\n);}sleep(3);return 0;
} 我们先用监控脚本对进程进行实时监控 我们可以看到子进程退出后父进程回收了子进程的退出信息回收了内存空间子进程也就不会变成僵尸进程了。
waitpid pid_ t waitpid(pid_t pid, int * status, int options); 返回值
当正常返回的时候waitpid返回收集到的子进程的进程ID如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数
pid Pid-1,等待任一个子进程。与wait等效。 Pid0.等待其进程ID与pid相等的子进程。status: WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码options: 当设置为WNOHANG时若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进程的ID。
例如
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/types.h
#include sys/wait.hint main()
{pid_t id fork();if (id 0){int cnt 5;while (cnt--){printf(我是子进程, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);}exit(0);}int status 0;pid_t ret_id waitpid(id, status, 0);printf(我是父进程等待子进程成功,pid: %d, ppid: %d\n, getpid(), getppid());return 0;
}注意
如果子进程已经退出调用wait/waitpid时wait/waitpid会立即返回并且释放资源获得子进程退出信息。如果在任意时刻调用wait/waitpid子进程存在且正常运行则进程可能阻塞。如果不存在该子进程则立即出错返回。
获取子进程status
wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。如果传递NULL表示不关心子进程的退出状态信息。否则操作系统会根据该参数将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待可以当作位图来看待不同比特位所代表的信息不同。 我们可以通过为操作查看根据status得到的进程的退出码和退出信号。 (status 8) 0xFF//退出码 status 0x7F//退出信号 如下
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/types.h
#include sys/wait.hint main()
{pid_t id fork();if (id 0){int cnt 5;while (cnt--){printf(我是子进程, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);}exit(111);}int status 0;pid_t ret_id waitpid(id, status, 0);printf(我是父进程等待子进程成功,pid: %d, ppid: %d, ret_id: %d, status: %d, child exit code: %d, child exit siginal: %d\n,getpid(), getppid(), ret_id, status, (status 8) 0xFF, status 0x7F);return 0;
} 注意退出信号为0则表示代码正常非0则表示代码异常。
非阻塞等待测试
父进程一直调用wait/waitpid进行等待这是阻塞等待。 而可以让父进程不用一直等待子进程退出而是当子进程未退出时父进程不占用资源做自己的事情当子进程退出时再读取子进程的退出信息即非阻塞等待那么如何做到呢 把waitpid 的第三个参数写成 WNOHANG 即可。
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h#define TASK_NUM 10// 预设一批任务
void sync_disk()
{printf(这是一个刷新数据的任务\n);
}void sync_log()
{printf(这是一个同步日志的任务\n);
}void sync_send()
{printf(这是一个进行网络发送的任务\n);
}typedef void (*func_t)();
func_t other_task[TASK_NUM] {NULL};int LoadTask(func_t func)
{int i 0;for (; i TASK_NUM; i){if (other_task[i] NULL)break;}if (i TASK_NUM)return -1;elseother_task[i] func;return 0;
}void InitTask()
{int i 0;for (i 0; i TASK_NUM; i){other_task[i] NULL;}LoadTask(sync_disk);LoadTask(sync_log);LoadTask(sync_send);
}void RunTask()
{int i 0;for (i 0; i TASK_NUM; i){if (other_task[i] NULL)continue;other_task[i]();}
}
int main()
{pid_t id fork();if (id 0){int cnt 5;while (cnt--){printf(我是子进程, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);}exit(111);}InitTask();while (1){int status 0;pid_t ret_id waitpid(id, status, WNOHANG);if (ret_id 0){printf(error\n);exit(1);}else if (ret_id 0){RunTask();sleep(1);continue;}else{if (WIFEXITED(status)){printf(wait success child exit code: %d\n, WEXITSTATUS(status));}else{printf(wait success child exit siginal: %d\n, status 0x7F);}}}return 0;
}四、进程程序替换
创建子进程的目的是什么 1、让子进程执行父进程的一部分代码 2、如果子进程想指向一个全新的程序代码便有了进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。 创建进程的时候OS 先把对应的数据结构内核的PCD空间先创建出来然后在需要的时候再通过 execl 把外部的代码录制到内存里、
替换函数
其实有六种以exec开头的函数,统称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[]);
代码演示
#include stdio.h
#include unistd.h
#include stdlib.h
#include sys/wait.h
#include sys/types.h
int main()
{pid_t id fork();if (id 0){// childprintf(我是子进程%d\n, getpid());// execl(/bin/ls, ls, -a, -l, NULL);char *const myargv[] {ls,-a,-l,-n,NULL};execv(/bin/ls, myargv);// execlp(ls,ls, -a, -l, NULL);// char *const myargv[] {// ls,// -a,// -l,// -n,// NULL};// execvp(ls,myargv);exit(1);}sleep(5);// faterint status 0;printf(我是父进程\n);waitpid(id, status, 0);printf(child exit code: %d\n, WEXITSTATUS(status));return 0;
}函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
l(list) : 表示参数采用列表v(vector) : 参数用数组p(path) : 有p自动搜索环境变量PATHe(env) : 表示自己维护环境变量 下图是exec函数族一个完整的例子
五、简易的shell
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表它随着时间的流逝从左向右移动。shell从用户读入字符串ls。shell建立一个新的进程然后在那个进程中运行ls程序并等待那个进程结束。 然后shell读取新的一行输入建立一个新的进程在这个进程中运行程序 并等待这个进程结束。 要写一个shell需要循环以下过程:
获取命令行解析命令行建立一个子进程fork替换子进程execvp父进程等待子进程退出wait
代码实现
#include stdio.h
#include assert.h
#include string.h
#include unistd.h
#include sys/wait.h
#include sys/types.h
#include stdlib.h#define MAX 1024
#define ARGC 64
#define SEP int split(char *commandstr, char *argv[])
{assert(commandstr);assert(argv);argv[0] strtok(commandstr, SEP);if (argv[0] NULL)return -1;int i 1;while (argv[i] strtok(NULL, SEP));// while (1)// {// argv[i] strtok(NULL, SEP);// if (argv[i] NULL)// break;// i;// }return 0;
}void debugPrint(char *argv[])
{int i 0;for (i 0; argv[i]; i){printf(%d : %s\n, i, argv[i]);}
}int main()
{char commandstr[MAX] {0};char *argv[ARGC] {NULL};while (1){printf([zhangsanmymachine currpath]#);fflush(stdout);char *s fgets(commandstr, sizeof(commandstr), stdin);assert(s);(void)s;commandstr[strlen(commandstr) - 1] \0;int n split(commandstr, argv);assert(n 0);if (n ! 0)continue;debugPrint(argv);pid_t id fork();assert(id 0);(void)id;if (id 0){// childexecvp(argv[0],argv);exit(1);}int status 0;waitpid(id, status, 0);// printf(%s\n,commandstr);}return 0;
}