怎么样做网站代理商,专业北京网站建设公司,响应式全屏网站,前端开发一年可以挣多少钱文章目录 创建进程fork创建进程fork返回值写诗拷贝fork常规用法fork失败的原因 进程终止进程正常终止查看进程退出码_exit函数exit函数exit 和 _exit 的区别return退出 进程等待进程等待的方式wait方法(系统调用)waitpid方法(系统调用) WEXITSTATUS 和 WIFEXITED阻塞等待和非阻… 文章目录 创建进程fork创建进程fork返回值写诗拷贝fork常规用法fork失败的原因 进程终止进程正常终止查看进程退出码_exit函数exit函数exit 和 _exit 的区别return退出 进程等待进程等待的方式wait方法(系统调用)waitpid方法(系统调用) WEXITSTATUS 和 WIFEXITED阻塞等待和非阻塞等待 进程程序替换替换函数execl函数execlp函数execle函数execv函数execvp函数execvpe函数 exec系列函数exec家族关系 创建进程
fork创建进程
fork函数
返回值子进程中返回0父进程返回子进程id出错返回-1。
进程调用fork当控制转移到内核中内核做
分配新的内存块和内核数据结构给子进程将父进程的部分数据结构内容拷贝给子进程将子进程添加到系统进程列表当中fork返回调度器开始调度 #include stdio.h
#include sys/types.h
#include unistd.h
int main()
{printf(before fork pid is :%d\n,getpid());pid_t id fork();if(id 0){perror(fork);}printf(after fork pid id :%d\n,getpid());printf(fork return value is :%d\n,id);return 0;
}上面程序运行结果 可以看出12行和14行中的打印信息被执行了两次而且两次的值也不一样。 因为fork之前父进程单独执行fork之后父子两个执行流分别执行。注意fork之后谁先执行完全由调度器 决定。
fork返回值
子进程返回0父进程返回的是子进程的id
写诗拷贝
fork之后父子进程共享代码父子再不写入时数据也是共享的当任意一方试图写入便以写时拷贝的方式各自一份副本。 fork常规用法
一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子 进程来处理请求一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。
fork失败的原因
系统中有太多的进程实际用户的进程数超过了限制
进程终止
进程退出的场景
代码跑完结果正确。代码跑完结果不正确代码没跑完进程出异常
进程正常终止
main返回调用_exit使用exit
再平时写C/C程序时一般都i会这样写
#include stdio.h
int main()
{//...return 0;
}都会在main函数最后一行写一句return 0; return 0就是进程的退出码一般0表示成功非0 表示失败。 一但程序执行失败需要知道原因用不同的数字来表示不同的失败原因。
查看进程退出码
使用echo $?可以查看最近一次进程的退出码 比如系统中没有lll命令执行然后查看进程退出码可以看到退出码为127 在C标准库函数中有一个strerror函数用于将错误码通常是由系统调用或库函数返回的错误码转换为对应的错误消息字符串。
#include stdio.h
#include string.h
int main()
{for (size_t i 0; i 255; i){printf(error code %d:%s\n,i,strerror(i));}return 0;
}Linux下错误码一共有133个。
_exit函数 参数status 定义了进程的终止状态父进程通过wait来获取该值 _exit在程序的任意位置都可以终止进程
#include stdio.h
#include unistd.h
void test()
{printf(Hello World\n);_exit(1);
}
int main()
{test();return 0;
}运行结果: main函数中的return 0还没有执行到进程就终止了。
exit函数
exit和_exit 的区别exit是c语言的进程终止的函数而_exit是Linux系统调用接口的函数c语言在实现exit函数时会封装_exit。 #include stdio.h
#include stdlib.h
void test()
{printf(Hello World\n);exit(1);
}
int main()
{test();return 0;
}进程退出码和上面一样都是1main函数中的return 0不会执行进程就终止了。
exit 和 _exit 的区别
eixt会刷新缓冲区 更推荐_eixt 不会刷新缓冲区。exit封装了_exit; 同样的代码分别使用eixt 和 _exit终止进程exit会刷新缓冲区。(printf语句没有加\n),而_exit则不会刷新缓冲区。
return退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。只有在main函数中return才能起到进程终止的作用在其他函数中return不会终止进程仅仅只是函数调用结束。
进程等待
进程等待的必要性
子进程退出父进程不等待子进程可能变僵尸从而造成内存泄漏一个进程一旦变为僵尸谁都无能为力kill也杀不掉因为无法杀掉一个已经死掉的进程。父进程创建子进程子进程的任务完成的情况我们需要知道。子进程的运行结果是否正确是否正常退出(比如ls命令 是bash的子进程ls的执行情况是需要我们知道的)父进程通过进程等待的方式回收子进程资源获取子进程退出信息。(父进程必须要做的)
进程等待的方式
wait方法(系统调用)
man 2 wait 认识wait 返回值 等待成功返回子进程的pid 等待失败返回-1。
函数参数: 输出型参数获取子进程退出状态,不关心则可以设置成为NULL
wait它等待任意子进程退出而不需要指定特定的子进程ID。如果没有子进程退出wait 会阻塞当前进程直到有子进程退出。(阻塞等待简单理解就是父进程啥也不干就只等子进程退出进行回收)
#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 ){perror(fork\n);}else if(id 0){//子进程printf(i am child process.pid %d\n,getpid());//子进程退出 退出码为1 exit(1);}else{//父进程printf(i am Parent process waiting...\n);sleep(3);//父进程等待三秒后回收子进程int status 0;pid_t waitid wait(status);//不想获取子进程退出码可以设置为NULL//等待失败处理if(waitid -1){perror(wait fail\n);exit(-1);}printf(wait sucess child excit code %d,waitid %d\n,status,waitid);}return 0;
}运行结果: 这里父进程获取到的子进程退出码并不是1而是256 这是因为status占4个字节32个比特位。前十六位不用关心后是6位前8位表示进程正常的退出码最后七位表示进程异常收到的信号退出码和信号中间的一位是core dump标志位和信号有关这里不用深究。 exit(1) 则是进程正常退出也没有收到异常信号status则是 这就是256的原因。 如果想直接拿到进程的退出码而不是status(status8) 0xff即可 上面代码31行修改为 printf(wait sucess child excit code %d,waitid %d\n,(status8)0xFF,waitid);运行结果 waitpid方法(系统调用)
等待指定进程或者任意子进程,相比wait更灵活 man 2 waitpid 认识waitpid 参数
pdi等待子进程的id若设置为-1则和wait等效status输出型参数获取子进程的退出状态不关心子进程退出状态设置为NULLoptions设置等待方式阻塞等待或者非阻塞等待
返回值
等待成功返回被等待进程的pid。等待方式设置为非阻塞等待(WNOHANG)而调用中waitpid发现没有已退出的子进程可收集,则返回0
阻塞等待示例
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include stdlib.h
int main()
{//waitpid阻塞等待pid_t id fork();if(id 0 ){perror(fork\n);exit(-1);}else if(id 0){//子进程printf(i am child process. pid %d\n,getpid());sleep(3);exit(1);}else {//父进程printf(parent process waiting ...\n);int status 0 ;pid_t ret waitpid(-1,status,0);//阻塞等待任意子进程if(WIFEXITED(status) ret id){//等待成功printf(wait sucess,child return code %d,ret %d\n,WIFEXITED(status),ret);}else{//等待失败printf(wait fail\n);}}return 0;
}运行结果 等待成功子进程退出码为1waitpid返回值为子进程的pid
WEXITSTATUS 和 WIFEXITED
WIFEXITED 和 WEXITSTATUS 是在 sys/wait.h 头文件中定义的两个宏用于处理子进程的退出状态信息。
WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码
非阻塞等待示例
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include stdlib.h
int main()
{//waitpid非阻塞等待pid_t id fork();if(id 0 ){//失败处理perror(fork\n);exit(-1);}else if(id 0){//子进程printf(i am child process. pid %d\n,getpid());sleep(3);exit(1);}else {//父进程printf(parent process waiting ...\n);int status 0;pid_t ret 0;do{ret waitpid(-1,status,WNOHANG);//非阻塞等待任意子进程if(0 ret){//子进程还没退出父进程非阻塞等待可以做其他事情printf(child process is running,parent do other things\n);sleep(1);}}while(0 ret);//等待成功if(WIFEXITED(status) ret id){//打印子进程退出码和waitpid返回值printf(wait sucess,child return code %d,ret %d\n,WEXITSTATUS(status),ret);}//等待失败else{printf(wait fail\n);}}return 0;
}运行结果 父进程非阻塞等待子进程时还可以做其他的事情 wiatpid返回值时子进程的id
阻塞等待和非阻塞等待
阻塞等待:
阻塞等待会导致进程无法执行其他任务直到等待的事件发生。 waitpid函数设置为阻塞等待只需将options参数设置为0 阻塞等待的优点 实现简单易于理解。不需要额外的代码来检查事件状态。 阻塞等待的缺点 整个进程或线程被挂起无法执行其他任务。可能导致资源浪费因为进程被阻塞时它可能无法充分利用系统资源
非阻塞等待
waitpid函数设置为阻塞等待需要将options参数设置为WNOHANG。(一个宏) 非阻塞等待的优点 可以充分利用系统资源因为在等待事件的同时可以执行其他任务。更灵活适用于需要同时处理多个任务的情况。 非阻塞等待的缺点 实现较为复杂需要额外的代码来轮询或处理事件通知。可能会增加系统负载因为需要周期性地检查事件状态
进程程序替换
进程程序替换是指一个正在运行的进程将自己的地址空间、代码、数据和堆栈等信息替换为另一个程序的内容。
用fork创建的子进程和父进程执行的是相同的程序但有可能执行不同的代码分支。一般fork创建的子进程需要调用exec函数来执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不会创建新的子进程所以调用exec前后该进程的id并未改变。程序替换是通过特定的接口加载到磁盘上的一个程序加载到调用进程的地址空间中 替换函数
有六种以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[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);execl函数
int execl(const char *path, const char *arg, ...);参数1path 是要执行的程序的路径(需要指定路径)。参数2是可变参数列表表示你要如何执行这个程序并以NULL结尾。 示例
#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){perror(fork fail\n);}else if(id 0){//子进程printf(i am child process, pid %d,getpid());//子进程执行ls -a 命令execl(/usr/bin/ls,ls,-a,NULL);printf(exec end\n);exit(1);}else{//父进程int status 0;pid_t ret waitpid(id,status,0);//阻塞等待指定子进程if(WIFEXITED(status) ret id)//等待成功{printf(wait sucess,child return code %d,ret %d, id %d\n,WEXITSTATUS(status),ret,id);}}return 0;
}运行结果 可以发现 当进程程序替换完成后exec后面的代码将不再执行。 一旦exec替换失败才会只执行后面的代码 比如将上面17行要替换的进程改为一个不存在的
execl(/usr/bin/lsl,ls,-l,NULL);执行结果子进程后面的代码被执行了。 execlp函数
int execlp(const char *file, const char *arg, ...);参数1需要执行的程序名称。只需要指定程序名称不需要指定路径参数2是可变参数列表表示你要如何执行这个程序并以NULL结尾。 示例子进程执行ls命令
execlp(ls,ls,-l,NULL);execle函数
int execle(const char *path, const char *arg, ..., char * const envp[]);参数1需要执行程序的路径 参数2是可变参数列表表示你要如何执行这个程序并以NULL结尾。 参数3envp是一个以 NULL 结尾的字符串数组用于设置新程序的环境变量。
// 设置新程序的环境变量
char *envp[] {MY_VARIABLEvalue, NULL};
// 使用 execle 函数替换当前进程的程序
execle(/bin/echo, echo, Hello, execle!, (char *)NULL, envp);运行结果 execv函数
int execv(const char *path, char *const argv[]);参数1path 是要执行的程序的路径。参数2argv 是一个以 NULL 结尾的指针数组其中包含新程序的名称和参数。
char *const argv[] {ls,-l,NULL};
execv(/usr/bin/ls,argv);运行结果 execvp函数
int execvp(const char *file, char *const argv[]);不用指定路径即可。
execvpe函数
int execvpe(const char *file, char *const argv[], char *const envp[]);第一个参数是要执行程序的路径第二个参数是一个指针数组数组当中的内容表示你要如何执行这个程序数组以NULL结尾第三个参数是你自己设置的环境变量。
exec系列函数
这些函数如果调用成功则加载指定的程序并从启动代码开始执行不再返回。如果调用出错则返回-1。exec函数只有成功的返回值没有失败的返回值。
exec家族关系