jsp简述网站开发流程,百度指数上多少就算热词,网站的建设及维护的费用,网站图片特效代码目录 一、进程创建
1、fork函数创建进程
2、fork函数的返回值
3、fork常规用法
4、fork调用失败的原因
二、进程终止
1、进程终止的方式
2、进程退出码
3、进程的退出方法
三、进程等待
1、进程等待的必要性
2、wait函数
3、waitpid函数
四、进程程序替换
1、概念…目录 一、进程创建
1、fork函数创建进程
2、fork函数的返回值
3、fork常规用法
4、fork调用失败的原因
二、进程终止
1、进程终止的方式
2、进程退出码
3、进程的退出方法
三、进程等待
1、进程等待的必要性
2、wait函数
3、waitpid函数
四、进程程序替换
1、概念
2、原理
3、进程替换函数 一、进程创建
在之前的学习中我们已经简单使用了fork函数创建一个进程。下面我们来具体讲一讲fork创建进程。
1、fork函数创建进程
在linux中fork函数是一个非常重要的函数它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。
返回值子进程中返回0父进程返回子进程pid创建进程出错则返回-1。
上面这些我们非常熟悉了。接着我们来看看下面的内容。 进程调用fork当控制转移到内核中的fork代码后内核做 1、分配新的内存块和内核数据结构给子进程 2、将父进程部分数据结构内容拷贝至子进程 3、添加子进程到系统进程列表当中 4、fork返回开始调度器调度 fork之后父子进程代码共享即fork之前的代码由父进程独立执行而fork之后的代码父子两个执行流分别执行。并且fork之后父进程和子进程谁先执行完全由调度器决定。
因为代码是只读的所以代码可以完全共享。虽然代码是父子共享的但是有的数据必须分离要各自都有一份。于是我们就要用到写时拷贝的技术而这我们已经在上一节进程地址空间中讲到了。
那么为什么我们不在创建进程的时候就直接拷贝分离呢下面我们来具体讲一讲。
我们创建子进程的目的是为了让它去帮助我们完成和父进程不一样的任务而不一样的任务所需要用到的数据可能是不一样的。而操作系统无法提前知道子进程需要什么数据来完成任务。比如有的数据是只读的所以父子进程完全可以共享而有的数据又可写为了不让父进程数据的修改影响子进程或者子进程数据的修改影响父进程我们要写时拷贝。
简单来说如果全部拷贝那么我们可能会将一些子进程不会用到的数据拷贝一份这样就浪费空间而且效率低下。即使要用到但是只是读取完全可以父子进程共享数据避免空间中有两份一模一样的数据。
所以将来会被父或子进程修改的数据值得拷贝。但即使是操作系统也无法预先知道谁会被访问修改所以要用写时拷贝技术。 2、fork函数的返回值
~ fork函数为什么有两个返回值
fork之后有两个执行流父子进程代码是共享的所以return会被调度两次被父子进程各自执行return的。
~ 为什么父进程返回子进程pid,给子进程返回0
父亲只有一个孩子可以有多个孩子找父亲具有唯一性。所以给父进程返回子进程pid便于标识子进程。
3、fork常规用法
1、一个父进程希望复制自己使父子进程同时执行不同的代码段。例如父进程等待客户端请求生成子进程来处理请求。 2、一个进程要执行一个不同的程序。例如子进程从fork返回后调用exec函数。
4、fork调用失败的原因
fork有时也会调用失败原因一般有两个1、系统中有太多的进程 2、实际用户的进程数超过了限制。
二、进程终止
1、进程终止的方式
1、代码跑完结果正确
2、代码跑完结果错误
3、代码没有跑完程序崩溃
2、进程退出码
首先我们来看一个问题我们在编写代码时在main函数的结尾我们总是 return 0。那么main函数返回值的意义是什么呢为什么总是返回0呢返回其他值行不行呢
实际上main函数只是用户级别代码的入口main函数也是被其他函数调用的所以当main函数调用结束后就应该给操作系统返回相应的退出信息而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回的。
一般以0表示代码成功执行完以非0表示代码执行过程中出现了错误。比如下面的代码 代码运行完后我们可以使用 echo $? 命令查看最近一个进程的退出码 因为在程序中运行成功了那就是成功了而如果失败了那么失败的原因有很多种。所以用0表示成功非0的不同值可以表示不同的错误。因此我们可以通过退出码来定位程序的错误原因。
我们使用strerror函数可以通过错误码获取该错误码在C语言当中对应的错误信息 注程序崩溃的时候退出码没有意义。
3、进程的退出方法
正常终止
~ return 返回return只有在main函数中表示直接终止进程并返回退出码在其他函数中表示返回值。
~ exit函数是一个库函数。在代码的任何地方调用都表示直接终止进程。并且exit函数在退出进程前会做一系列工作
1、执行用户通过atexit或on_exit定义的清理函数。 2、关闭所有打开的流所有的缓存数据均被写入。 3、调用_exit函数终止进程。
如下面的代码因为exit是库函数exit终止进程前会将缓冲区当中的数据输出。 ~ _exit是系统调用函数。_exit函数也可以在代码中的任何地方退出进程但是_exit函数会直接终止进程并不会在退出进程前会做任何收尾工作。
如下面的代码缓冲区当中的数据将不会被输出。 ~ exit 和 _exit 的区别
从下面的图中我们可以知道exit会刷新缓冲区的数据而_exit不会刷新。 异常终止
1、使用 ctrlc或者kill -9使进程异常退出。
2、代码错误导致进程运行时异常退出。例如代码当中存在野指针问题使得进程运行时异常退出或是出现除0情况使得进程运行时异常退出等。
三、进程等待
1、进程等待的必要性
1、子进程退出父进程如果不读取子进程的退出信息子进程就会变成僵尸进程进而造成内存泄漏。
2、进程一旦变成僵尸状态那么即使是kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。
3、因为子进程是由父进程创建出来帮助父进程完成任务的所以进程完成任务后父进程有必要知道子进程将任务完成得怎么样了。
4、父进程通过进程等待的方式回收子进程资源获取子进程退出信息。
2、wait函数
原型pid_t wait(int* status) 可以等待任意子进程等待成功返回等待子进程pid失败返回-1。status输出型参数获取子进程退出状态不关心则可以设置成为NULL。 当子进程退出后父进程读取了子进程的退出信息子进程也就不会变成僵尸进程了。 3、waitpid函数
原型pid_t waitpid(pid_t pid, int* status, int options) 等待指定子进程或任意子进程。
返回值 1、等待成功返回被等待进程的pid。 2、如果设置了选项WNOHANG而调用中waitpid发现没有已退出的子进程可收集则返回0。 3、如果调用中出错则返回-1这时errno会被设置成相应的值以指示错误所在。
参数pidPid-1等待任一个子进程。与wait等效。Pid0等待其进程ID与pid相等的子进程。
status该参数是一个输出型参数由操作系统填充 ,如果传递NULL表示不关心子进程的退出状态信息。否则操作系统会根据该参数将子进程的退出信息反馈给父进程。
options 若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进程的ID。 获取子进程status
status就是将子进程的退出信息传递给父进程的重要参数。但是它不能简单的当作整形来看待。因为它的32个比特位中只有次低8位表示进程退出码信息。如下图 因此我们只能通过下面的代码来获取子进程的正确退出码 获取子进程的终止信号
status参数的低7位表示子进程的终止信号我们可以通过下面的代码获得。
status 0x7f
除了上面的我们可以自己写代码进行退出码的获取我们还可以使用提供的宏进行退出码的获取
WIFEXITED(status): 若为正常终止子进程返回的状态则为真非0。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码 父进程拿到子进程的退出码
既然进程之间是相互独立的而且退出码也还是子进程的数据那么为什么父进程可以通过wait和waitpid函数拿到子进程的退出信息它是怎么拿到的呢
子进程变成僵尸进程后虽然子进程已经退出了但是子进程的PCB任然会保留下来子进程的task_struct 里面保留了该子进程退出时的退出结果信息。所以父进程可以通过wait和waitpid函数读取子进程task_struct 中的退出结果信息拿到子进程的退出码和退出信号。
options
默认为0代表父进程阻塞等待WNOHANG代表父进程非阻塞等待。 potions传入WNOHANG等待的子进程若是没有结束那么waitpid函数将直接返回0不予以等待。而等待的子进程若是正常结束则返回该子进程的pid。 阻塞等待当子进程未退出时父进程都在一直等待子进程退出在等待期间父进程不能做任何事情这种等待叫做阻塞等待。
非阻塞等待父进程没有一直等待子进程退出而是当子进程未退出时父进程可以做一些自己的事情当子进程退出时再读取子进程的退出信息这种等待就叫非阻塞等待。
四、进程程序替换
1、概念
我们知道在父进程使用fork函数创建了一个子进程后父子进程共享代码。也就是说子进程使用的是父进程的代码。那么如果子进程要执行和父进程不一样的代码该怎么办呢这就需要使用进程的程序替换了。
进程程序替换顾名思义就是使用一个新的程序替换原有的程序进程将执行新程序的代码而不再执行原有程序的代码。通过特定的接口加载磁盘上的一个全新的程序加载到调用进程的地址空间中
2、原理 如上图所示将磁盘上的 test2.exe 加载到内存并和 test1.exe的页表重新建立映射关系这时进程的代码和数据就变成了 test2.exe的这就是进程替换。进程替换可以通过系统调用接口实现。
3、进程替换函数
execl 函数
函数原型int execl(const char *path, const char *arg, ...)。我们知道运行一个程序的前提是要先找到该程序所以该函数的第一个参数是程序的路径。第二个参数是可变参数列表表示你要如何执行这个程序并以NULL结尾。
execlp函数
函数原型int execlp(const char *file, const char *arg, ...) 。第一个参数是要执行程序的名字第二个参数是可变参数列表表示你要如何执行这个程序并以NULL结尾。
execle函数
函数原型int execle(const char *path, const char *arg, ..., char *const envp[])。第一个参数是要执行程序的路径第二个参数是可变参数列表表示你要如何执行这个程序并以NULL结尾第三个参数是你自己设置的环境变量。
execv函数
函数原型int execv(const char *path, char *const argv[])。第一个参数是要执行程序的路径第二个参数是一个指针数组数组当中的内容表示你要如何执行这个程序数组以NULL结尾。
execvp函数
函数原型int execvp(const char *file, char *const argv[])。第一个参数是要执行程序的名字第二个参数是一个指针数组数组当中的内容表示你要如何执行这个程序数组以NULL结尾。
execve函数
int execve(const char *path, char *const argv[], char *const envp[])。第一个参数是要执行程序的路径第二个参数是一个指针数组数组当中的内容表示你要如何执行这个程序数组以NULL结尾第三个参数是你自己设置的环境变量。