怎么做网站滑动图片部分h5,成都装修公司投诉平台,怎么把自己的产品推广出去,网站开发要用到什么#x1f9d1;#x1f4bb;作者#xff1a; 情话0.0 #x1f4dd;专栏#xff1a;《Linux从入门到放弃》 #x1f466;个人简介#xff1a;一名双非编程菜鸟#xff0c;在这里分享自己的编程学习笔记#xff0c;欢迎大家的指正与点赞#xff0c;谢谢#xff01; 进… 作者 情话0.0 专栏《Linux从入门到放弃》 个人简介一名双非编程菜鸟在这里分享自己的编程学习笔记欢迎大家的指正与点赞谢谢 进程退出和等待 前言一、进程创建1.1 fork函数1.2 写时拷贝1.3 fork常规用法1.4 fork调用失败的原因 二、进程退出2.1 进程退出场景2.1.1 查看退出码2.1.2 退出码的含义 2.2 如何理解进程退出2.3 进程退出的方式 三、进程等待3.1 进程等待的原因3.2 什么是进程等待3.3 进程等待的方式3.3.1 wait方法3.3.2 waitpid方法 3.4 子进程退出状态3.5 非阻塞式等待 总结 前言
之前的几篇博客已经是对进程的相关概念做了详细了解现阶段对进程的定义为内核数据结构加上该进程对应的代码和数据操作系统对进程通过先描述再组织的方式做管理。有了这些预备知识接下来就是要学习如何控制进程也就是在操作上该怎么做。 一、进程创建
1.1 fork函数 关于fork函数的知识此篇博客有详细介绍进程创建 进程调用fork当控制转移到内核中的fork代码后内核做 分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork返回开始调度器调度 在调用fork函数之后系统会将父进程的代码拷贝一份给子进程同时会有两个执行流分别执行父进程和子进程要注意的是子进程不会去执行fork之前的代码。 1.2 写时拷贝 父子进程代码共享父子再不写入时数据也是共享的当任意一方试图写入便以写时拷贝的方式各自一份副本。具体见下图: 在修改内容之前父子进程的在物理内存页的数据、代码指向同一块位置如子进程对数据进行修改那么此时就会发生写时拷贝在物理内存页重新开辟一块空间将修改后的数据存入其中。 因为在操作系统是不允许空间的浪费所以不会将父进程的所有代码数据都在物理内存中重新拷贝一份而是通过写时拷贝的方式在子进程需要使用(修改)数据的时候才会重新开辟空间它是一种按需申请资源的策略。 1.3 fork常规用法
一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。
1.4 fork调用失败的原因
系统中有太多的进程实际用户的进程数超过了限制
二、进程退出
2.1 进程退出场景 a. 正常运行完毕1. 结果正确 2. 结果不正确 b. 崩溃了(进程异常) 崩溃的本质进程因为某些原因导致进程收到了来自操作系统的信号(kill -9) 2.1.1 查看退出码
我们一般在写C语言程序都会在main函数结束时返回 0这个0代表着该进程的退出码在linux中可通过这样的指令查看进程的退出码echo $?。看下面代码
int add_to_top(int num)
{int sum0;for(int i1;inum;i){sumi;}return sum;
}int main()
{int retadd_to_top(100);if(ret5050)return 1;else return 0;
}上面的代码要实现的功能从1加到100若和为5050则返回1否则返回0。通过下图可以看到该进程的退出码为1表示结果正确。但是奇怪的是后两次的查看退出码都为了0这是因为该指令只会保留最近一次执行的进程的退出码后两次代表着该条指令执行后的退出码。
2.1.2 退出码的含义
我们看到的退出码都是数字对于程序员来说我们可能知道一些退出码所代表的含义但是对于一般人来说看到这些数字并不了解所蕴含的意义。所以对于一般人来说如果你只给他退出码是没有价值因为他并不知道这些退出码代表的含义。关于退出码的含义我们可以自定义下面看一下C语言所提供的退出码的含义。
int main()
{for(int i0;i200;i){printf(%d:%s\n,i,strerror(i));}return 0;
}这只是前二十个后面还有更多。当然这是在linux操作系统下在windows下所提供的退出码含义是不同的。 2.2 如何理解进程退出
关于进程的退出可以理解的是操作系统内少了一个进程操作系统要释放进程对应的内核数据结构代码和数据。
2.3 进程退出的方式
main函数return。而其他函数的return仅仅代表该函数的返回。对于这种方式来说进程执行本质是main执行流执行当main函数执行完时代表着进程也就结束了。exit函数退出。exit函数所包含的数字为该进程的退出码在函数任意位置调用直接使进程退出。_exit函数退出。直观感觉上和exit的功能是一样的但是在一些细节是不一样的。exit函数在退出的时候会自动刷新缓冲区而_exit函数不会刷新缓冲区。它们两个的关系是一种包含和被包含的关系。从下面这个图可以得到一个暗藏的点缓冲区不在操作系统内。 三、进程等待
3.1 进程等待的原因 之前讲过若子进程先退出而父进程并没有读取子进程状态就可能造成‘僵尸进程’的问题进而造成内存泄漏。进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。我们为什么要创建子进程目的就是为了让子进程帮助我们去完成某些事情关于父进程派给子进程的任务完成的情况可能我们不会关心完成的对不对也可能会关心子进程运行完成的结果对还是不对亦或是否正常退出。 避免内存泄漏必获取子进程的执行结果。可能 关于子进程的退出结果有三种可能性 a. 代码跑完结果对 b. 代码跑完结果不对 c. 代码运行异常 关于结果对或不对可以通过退出码的方式判别代码运行异常则是收到某种信号。因此衡量一个进程运行的怎样是通过退出码信号的方式来执行的。 3.2 什么是进程等待
通过系统调用获取子进程退出码或者退出信号的方式同时释放内存问题。
3.3 进程等待的方式
3.3.1 wait方法
pid_t wait(int *status);返回值成功返回被等待进程pid失败返回-1。 参数输出型参数获取子进程退出状态,不关心则可以设为NULL //代码功能父进程在休眠5秒的过程中子进程先运行2秒然后子进程退出2秒之后父进程对子进程做进程等待操作。
int main()
{pid_t retfork();if(ret0){//子进程int cnt2;while(cnt--){printf(我是子进程我现在活着呢我离死亡还有%d秒,pid:%d,ppid:%d\n,cnt,getpid(),getppid());sleep(1);}_exit(0);}sleep(5);//父进程pid_t ret_idwait(NULL);printf(我是父进程等待子进程成功,pid:%d,ppid:%d\n,getpid(),getppid());return 0;
}在运行代码之后我们应该观察到的现象父子进程的状态最开始都为运行状态子进程经2秒输出2条语句然后退出变为僵尸状态父进程依然为运行状态再过3秒之后父进程对子进程等待回收然后全部退出。 3.3.2 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。 3.4 子进程退出状态
在 wait 和 waitpid 中都有一个status参数该参数是一个输出型参数由操作系统填充。它的功能是为了获取子进程的退出状态。如果传递NULL表示不关心子进程的退出状态信息。否则操作系统会根据该参数将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特位 通过对上图理解我们应该明白关于子进程的退出状态。如果进程是正常退出那么status位图的低八位为0次低八位为进程的退出状态也就是通过这次低八位获取进程的退出码。如果进程是被某种信号所杀而导致的异常退出则只需要关心低七位读到的结果为导致该进程退出的终止信号所对应的数字coredump标志位目前不需要了解。
int main()
{pid_t idfork();if(id0){//子进程int cnt2;while(cnt--){printf(我是子进程我现在活着呢我离死亡还有%d秒,pid:%d,ppid:%d\n,cnt,getpid(),getppid());sleep(1);}// int a10;// a/0;_exit(123);}sleep(5);int status0;pid_t ret_idwaitpid(id,status,0);printf(我是父进程等待子进程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n,getpid(),getppid(),(status0x7F),((status8)0xff));return 0;
}看上面这段代码如果按照这样的逻辑那么最终的运行结果为只看退出状态父进程获取到子进程的退出信号肯定为0因为是正常退出退出状态则为数字123若将那两条注释的代码取消那么子进程就会因为除0操作导致异常退出那么此时父进程就会读到对应的退出信号输出结果为该信号对应的数字。 父进程是如何获取子进程的退出状态信息的呢子进程有自己的PCB、地址空间、页表和内存而在PCB的内部会有两个属性exit_code、exit_signal。当子进程执行完毕时将main函数的返回值写到 exit_code 中如果出现异常操作系统则将遇到信号所对应的数字编号写到 exit_signal 中。当子进程退出后操作系统会将这份PCB维护起来所以就需要通过wait/waitpid这样的系统调用接口将从这份PCB读到的这两个属性以上面那种位图的方式设置到status参数中。父进程在wait的时候如果子进程没退出那父进程在干什么在子进程没有退出的时候父进程只能一直在调用waitpid进行等待——阻塞等待。
3.5 非阻塞式等待
waitpid(id,status,WNOHANG)上一小节的 waitpid 方法为阻塞等待而非阻塞等待与阻塞等待的区别在于第三个参数的不同阻塞等待是在子进程还没有退出的时候父进程只能一直等待直到子进程退出非阻塞等待是子进程还没有退出时父进程可以干一些其他事情而不是什么事情不干就在等待子进程退出。 下面这段代码将通过非阻塞的形式让父进程在还未等待到子进程的退出信息的时候去执行其他事情。
#define TASK_NUM 10
void sync_disk()
{printf(这是一个刷新数据的任务!\n);
}
void sync_log()
{printf(这是一个同步日志的任务!\n);
}
void net_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;else other_task[i] func;return 0;
}
void InitTask()
{for(int i 0; i TASK_NUM; i) other_task[i] NULL;LoadTask(sync_disk);LoadTask(sync_log);LoadTask(net_send);
}
void RunTask()
{for(int i 0; i TASK_NUM; i){if(other_task[i] NULL) continue;other_task[i]();}
}
int main()
{pid_t idfork();if(id0){//子进程int cnt5;while(cnt--){printf(我是子进程我现在活着呢我离死亡还有%d秒,pid:%d,ppid:%d\n,cnt,getpid(),getppid());sleep(1);}_exit(123);}InitTask();while(1){int status0;pid_t ret_idwaitpid(id,status,WNOHANG);if(ret_id-1){printf(等待错误!\n);break;}else if(ret_id0){//子进程还未退出父进程执行RunTask函数RunTask();sleep(1);}else{if(WIFEXITED(status))//正常退出{printf(我是父进程等待子进程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n,getpid(),getppid(),(status0x7F),WEXITSTATUS(status));}else//非正常退出printf(我是父进程等待子进程成功,pid:%d,ppid:%d,status signal:%d,status code:%d\n,getpid(),getppid(),(status0x7F),((status8)0xff));break;}}return 0;
}在子进程正常退出并且父进程等待成功的时候可以通过宏的方式来获取子进程的退出码之前的方法优雅度或者可扩展性都不太好当WIFEXITED(status)为真的时候通过WEXITSTATUS(status)获取退出码若不为真也就是异常退出时只能使用以前的方法。 总结
总结 本文深入探讨了操作系统中进程管理的三个核心方面进程的创建、退出和等待。首先我们了解了进程创建的过程它涉及到操作系统如何为新进程分配必要的资源包括内存空间和处理器时间并初始化进程表以跟踪和管理进程状态。接着我们讨论了进程退出的不同方式如正常退出、异常退出以及由于接收到信号导致的退出每种方式都对系统稳定性和资源管理产生不同的影响。 最后我们详细分析了进程等待的概念即一个进程可能需要暂停执行直到满足特定条件。这可能包括等待I/O操作完成、等待获取资源或等待其他进程的结束。文章强调了实现有效等待机制的重要性并指出了同步和通信在确保系统资源合理利用和进程间顺畅协作中的关键作用。 通过这篇博客我们不仅学习了关于进程操作的基本知识还加深了对于操作系统内部机制如何协同工作的理解。这些内容为我们进一步研究计算机科学的其他领域打下了坚实的基础。