第二章营销型网站建设测验,专业网站的定义,论述市场营销对网站设计的影响,wordpress指定分类不显示文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建
1、fork函数
fork函数从已存在进程中创建一个新进程#xff0c;新进程为子进程#xff0c;原进程为父进程。
#include unistd.h
pid_t fork(vo…
文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建
1、fork函数
fork函数从已存在进程中创建一个新进程新进程为子进程原进程为父进程。
#include unistd.h
pid_t fork(void);返回值子进程中返回0父进程返回子进程id出错返回-1。fork会有两个返回值这个上一篇已经写了原因。
进程调用fork当控制转移到内核中的fork代码后内核做 分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程不会完全一样 添加子进程到系统进程列表当中 fork返回开始调度器调度 两个进程都没有修改数据的时候都指向同一块物理内存空间只有一方尝试修改数据的时候才会再开一个空间。
写时拷贝
通常父子代码共享父子在不写入时数据也是共享的父进程按照自己的模板给子进程创建了虚拟内存创建了页表然后指向物理内存中同样的数据当子进程修改数据时系统就会拷贝一下子进程的数据进行修改并改变页表的映射关系最后就指向了一个新的物理内存空间。
存在写时拷贝的意义在于系统不允许不高效的程序出现父进程中子进程不需要的数据子进程也不会去读取当子进程要用到另外的空间时写时拷贝才会出现本质上这是一种资源筛选。
fork常规用法 一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。 一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数 fork调用失败的原因 系统中有太多进程 实际用户的进程数超过了限制 2、进程终止
1、情况分类
程序正常退出可以有结果正确或者不正确地退出。 程序崩溃本质是进程因为某些原因收到了操作系统给的信号比如之前的kill -9。
对于结果是否正确可以看程序退出码。之前我们会习惯地写int main() return 0;如果结果正确就返回0如果不正确就会返回非0这个不正确的信息会展现给用户来判断程序的错误。 1 mytest:mytest.c2 gcc -o $ $^ 3 .PHONY:clean4 clean:5 rm -f mytest
$表示目标文件mytest $^表示依赖文件mytest.c 1 #include stdio.h2 3 int add_to_top(int top)4 {5 int sum 0;6 for(int i 0; i top; i)7 {8 sum i;9 }10 return sum;11 }12 13 int main()14 {15 int result add_to_top(100);16 if(result 5050) return 0;17 else return 1; 18 }
一段简短的代码。然后变成可执行程序。因为没写输出所以我们看不到结果但是可以通过echo $?命令来查看程序退出码 但是多次使用后就没有用了因为这个命令只保留最近一次的退出码。因为上一个echo $?执行成功所以返回0。 操作系统对于不同的错误都有对应的退出码但给到用户的不能是一个数字而是给错误信息。看一下具体的退出码。 1 #include stdio.h2 #include string.h 3 4 int add_to_top(int top)5 {6 int sum 0;7 for(int i 0; i top; i)8 {9 sum i;10 }11 return sum;12 }13 14 int main()15 {16 for(int i 0; i 200; i)17 {18 printf(%d: %s\n, i, strerror(i));19 }20 //int result add_to_top(100);21 // if(result 5050) return 0;22 //else return 1;23 } 总共提供了133个错误代码。
但并不是退出码和错误信息一定会对应。
2、如何理解进程终止
进程退出时系统就需要释放对应的内核数据结构 代码和数据
3、进程终止的方式
除了main函数return结束我们也可以用exit函数结束。 可以直接退出exit里面的数字就是退出码。并且即使exit在调用的函数里面也会直接退出不再执行下面的代码。所以exit在代码的任何地方都可以退出进程。需要加上头文件stdlib.h。
另外一个 貌似和exit一样。但从内部来讲exit是进行完缓冲区的数据进行完操作后才退出而_exit是不管不顾直接找系统干掉这个进程不等缓冲区刷新。库函数实现的代码里exit是封装了_exit。
3、进程等待
子进程退出时如果父进程不去回收就会变成僵尸进程会造成内存泄漏。僵尸状态的进程是无法被杀死的。
一个进程的执行是要结果的子进程结束后用户得需要知道它的状态代码正常跑完可以通过退出码来知道运行异常可以通过抛出的信号来知道所以要想知道子进程执行的结果就要知道退出码和是否抛出异常。
所以进程等待就是通过系统调用获取子进程退出码或者退出信号的方式顺百年释放内存问题。
进程等待有两种方式。wait和waitpid。wait会等待所有父进程创建的子进程如果不传参传NULL那么就不管结果只回收。可以写这样一段代码来展现等待过程。 30 pid_t id fork();31 if(id 0)32 {33 //子进程34 int cnt 0;35 while(cnt)36 {37 printf(我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n, cnt--, getpid(), getppid());38 sleep(1);39 }40 exit(0);41 }42 sleep(10);43 //父进程44 pid_t ret_id wait(NULL);45 printf(我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d\n, getpid(), getppid(), ret_id);46 sleep(5);47 return 0; 子进程退出时父进程在等待所以就可以回收子进程。
如果要获取退出结果就要用到waitpid。 参数里pid 0表示等待指定的进程pid -1等待任一个子进程与wait等效。如果等待成功就会把等待的这个进程的pid返回失败就返回-1.
第二个参数status是输出型参数用来获取子进程的退出状态它的退出状态就是上面3个结果代码正常结束和抛出异常所以这个参数就是来接收这些的。关于退出时返回的信号可以用kill -l查看总共64个信号前面一半是常用的不过没有0号信号所以信号也是数字。乍一看status是要接收2个整数但实际上不应以整数角度看待它要以位图结构来看待。位图结构简而言之就是看二进制位status是4个字节32个比特位。 改一下之前的代码 上面cnt是5。exit括号里是10但是status的结果并不是10.这时候出现数字的把它写成二进制数左面16不看后面16个左面的8个是退出状态也就是高地址的8位这里的就是return的或者exit括号里的数字最低的7个位如果是0就是正常退出然后再看退出码来判断结果是否正确。最后还剩一位是core dump标志。那我们获取这两个数。 这样code就是结果是否正确signal就是正常退出。如果在子进程那里有一些异常程序打印一次就退出了父进程就回收它了比如野指针等问题。把while条件改成while(1)也可以用kill -9杀死程序父进程就回收它了。
父进程是如何获取子进程信息的一个进程有自己的pcb地址空间等等进程的pcb也就是task_struct结构体在结构体里有两个变量对应的就是返回值和退出信号。当进程结束时系统会把pcb维护起来。waitpid是系统调用接口能够访问到这个进程的pcb然后把数据拿到再返回给用户即可。
子进程在没有退出前父进程会一直等待子进程死亡这也就是一种阻塞等待。此时父进程不在运行状态所以父进程没有运行它在阻塞队列中待着。等到子进程结束后pcb中某一个指向父进程的指针会去找父进程父进程因此用阻塞状态变成运行状态来到运行队列中然后调用waitpid回收子进程。
用wait的时候默认是阻塞式调用而waitpid则是在等待过程中让父进程去做其他事保持运行状态这是非阻塞轮询。非阻塞时调用时有三个状态一个正常结束一个出错一个正在运行。如果成功就返回子进程pid如果第三个参数被设置那就是非阻塞式调用那么进程存在且正在运行就返回0出错返回-1.
改一下代码变成非阻塞 为了让父进程做其他事情还可以有其他办法先放下现在的代码 1 #include stdio.h2 #include string.h3 #include stdlib.h4 #include unistd.h5 #include sys/types.h6 #include sys/wait.h 19 int main()20 {21 /*for(int i 0; i 200; i)22 {23 printf(%d: %s\n, i, strettot(i));24 exit(123);25 }*/26 //int result add_to_top(100);27 //if(result 5050) return 0;28 //else return 1;29 pid_t id fork();30 if(id 0)31 {32 //子进程33 int cnt 5;34 while(cnt)35 {36 printf(我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n, cnt--, getpid(), getppid());37 sleep(1);38 }39 if(1 1) exit(0);40 exit(10);41 }42 //父进程43 while(1)44 {45 int status 0;46 pid_t ret_id waitpid(id, status, WNOHANG);47 if(ret_id 0)48 {49 printf(waitpid error!\n); 50 exit(1);51 }52 else if(ret_id 0)53 {54 printf(子进程还没有退出我在做做其他事情\n);55 sleep(1);56 continue;57 }58 else59 {60 printf(我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d child exit code: %d, child exit signal: %d\n, getpid(), getppid(), ret_id, (status8)0xFF, status 0x7F);61 break;62 }63 64 }65 }人为放一些任务函数让父进程去调用 并且写一些别的可调用的函数让父进程去做点事 19 #define TASK_NUM 1020 //预设一些任务21 void sync_disk()22 {23 printf(这是一个刷新数据的任务\n);24 }25 26 void sync_log()27 {28 printf(这是一个同步日志的任务\n);29 }30 31 void net_send()32 {33 printf(这是一个网络发送的任务\n);34 }35 36 //要保存的任务相关的37 typedef void(*func_t)();38 func_t other_task[TASK_NUM] {NULL};39 40 41 int LoadTask(func_t func)42 {43 int i 0;44 for(; i TASK_NUM; i)45 {46 if(other_task[i] NULL) break;47 }48 if(i TASK_NUM) return -1;49 else other_task[i] func;50 return 0;51 }52 53 void InitTask()54 {55 for(int i 0; i TASK_NUM; i)56 {57 other_task[i] NULL;58 }59 LoadTask(sync_disk);60 LoadTask(sync_log);61 LoadTask(net_send);62 }63 64 void RunTask()65 {66 for(int i 0; i TASK_NUM; i)67 {
W 68 if(other_task[i] NULL) continue;69 other_task[i]();70 }71 }在//父进程之后我们这样写 96 InitTask();97 //父进程98 while(1)99 {100 int status 0;101 pid_t ret_id waitpid(id, status, WNOHANG);102 if(ret_id 0)103 {104 printf(waitpid error!\n);105 exit(1);106 }107 else if(ret_id 0)108 {109 printf(子进程还没有退出我在做做其他事情\n);110 RunTask();111 sleep(1);112 continue;113 }114 else115 {116 printf(我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d child exit code: %d, child exit signal: %d\n, getpid(), getppid(), ret_id, (status8)0xFF, status 0x7F);117 break;118 }119 }之前获取信号和返回码的时候我们是自己的写的代码也可以用给的宏来获取。 下一篇继续写进程相关的知识。
结束。