手机网站 微信小程序,营销公司排名,网站建设公司70hf,wordpress前台增加编辑目录
一、进程是什么
二、task_struct
三、查看进程
四、创建进程
4.1 fork函数的认识
4.2 2. fork函数的返回值
五、进程终止
5.1. 进程退出的场景
5.2. 进程常见的退出方法
5.2.1 从main返回
5.2.1.1 错误码 5.2.2 exit函数
5.2.3 _exit函数
5.2.4 缓冲区问题补…目录
一、进程是什么
二、task_struct
三、查看进程
四、创建进程
4.1 fork函数的认识
4.2 2. fork函数的返回值
五、进程终止
5.1. 进程退出的场景
5.2. 进程常见的退出方法
5.2.1 从main返回
5.2.1.1 错误码 5.2.2 exit函数
5.2.3 _exit函数
5.2.4 缓冲区问题补充为什么_exit不刷新缓冲区
六、进程等待
6.1. wait和waitpid等待回收子进程
6.2 阻塞与非阻塞 Linux专栏传送门
一、进程是什么
在操作系统中进程是资源分配和独立运行的基本单位。它是程序在一个数据集合上运行的过程它是系统进行资源分配和调度的一个独立单位。单纯的只看一个定义很难理解什么是进程 我们磁盘中的可执行程序CPU要想拿到并且执行代码和数据要先放在内存中。操作系统是一个软件在内存中当磁盘中的可执行程序被内存拿到可执行程序的代码和数据会被内存拿到内存中的操作系统会对代码和数据进行描述然后组织为数据结构先组织在描述形成内核数据结构对象对进程的管理就变成了对数据结构对象的增删查改。 内核数据结构对象可以称为PCB也叫做进程控制块。
内核数据结构对象通过指针指向本身的代码和数据也指向下一个结构体进而形成真正的进程。进程内核数据结构对象自己的代码和数据。 这个数据结构就是进程列表。CPU对进程列表进行调度。
二、task_struct
在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的⼀种数据结构它会被装载到RAM(内存)里并且包含着进程的信息。
标示符: 描述本进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。优先级: 相对于其他进程的优先级。程序计数器: 程序中即将被执行的下一条指令的地址。内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息
三、查看进程
进程的信息可以通过/proc 系统文件夹查看 如要获取PID为1的进程信息你需要查看/proc/1 这个文件夹。
大多数进程信息同样可以使用top和ps这些用户级工具来获取 #include stdio.h
#include sys/types.h
#include unistd.h
int main()
{while(1){sleep(1);}return 0;
}
通过系统调用获取进程标示符
• 进程idPID • 父进程idPPID #include stdio.h
#include sys/types.h
#include unistd.h
int main()
{printf(pid: %d\n, getpid());printf(ppid: %d\n, getppid());return 0;
} 四、创建进程
4.1 fork函数的认识
在linux中fork函数非常重要 它从已存在的进程中创建一个新的进程 新进程为子进程而原进程为父进程。 #includeunistd.h
pid_t fork(void);
返回值子进程中返回0 父进程中返回子进程id, 出现错误则返回-1进程调用fork,当控制转移到内核中的fork代码后 内核会做下面几件事情
分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表中fork返回 开始调度器调度
当一个进程调用fork()之后就有两个二进制代码相同的进程。而且他们都运行到相同的地方。但每个进程都将可以开始他们自己的旅程。
这里我们看到了三行输出 一行before, 两行after父进程先打印before消息然后它又打印after另一个after消息有子进程打印的但是子进程没有打印before为什么呢
所以 fork之前父进程独立执行fork之后父子两个执行流分别执行注意fork之后谁先执行由调度器决定。
4.2 2. fork函数的返回值
子进程返回0父进程返回的是子进程的pid。
父进程与子进程是一对多的关系父进程子进程1N。对于子进程来说它只有一个父进程对于父进程来说他有多个父进程。为了管理这些进程所有父进程返回子进程的PID具有唯一性。子进程要想找到父进程肯定更方便。
为什么有两个返回值 因为fork之后是两个不同的进程 而返回值也是给不同的进程。 #include stdio.h
#include stdlib.h
#include unistd.h int main() { pid_t pid fork(); // 执行 fork() if (pid 0) { // 处理 fork 失败的情况 perror(fork failed); exit(EXIT_FAILURE); } else if (pid 0) { // 子进程执行 printf(This is the child process (PID: %d)\n, getpid()); } else { // 父进程执行 printf(This is the parent process (PID: %d, Child PID: %d)\n, getpid(), pid); } return 0; // 所有进程执行完毕
}执行fork函数会为子进程创建一个和父进程一样的PCB。但是他们所指向的代码都是一样的所以他们的代码共享。也就是说父进程和子进程共享代码中return都执行return。
五、进程终止
本质释放系统资源 就是释放进程申请的相关内核数据结构和对应的代码和数据。
5.1. 进程退出的场景
代码运行完毕 结果正确代码运行完毕结果不正确代码异常终止
5.2. 进程常见的退出方法
5.2.1 从main返回
除了main函数的返回值表示进程结束其它函数的return都表示函数结束。
5.2.1.1 错误码
main函数的返回值是返回给父进程或者系统的命令行中获取最近一个进程的返回值我们可以使用echo $?来获取
对于返回值0表示成功 非0表示错误为什么会失败呢系统提供了不同的错误码信息记录了错误的原因 也可以自己约定错误码。
那么什么是错误码呢
举个栗子
如果想要查看错误码 我们可以使用errno函数 使用man可以查看命令详情。 man 3 errno
如果想知道具体的错误内容 可以使用strerror函数参数传递错误码。 man 3 strerror 5.2.2 exit函数
在代码的任何地方 让进程直接结束。参数就是返回的错误码。
如果使用exit, 如果缓冲区有数据 则会被刷新出来。
5.2.3 _exit函数
是系统层的进程终止调用
如果使用_exit 缓冲区的数据则不会被刷新出来。
5.2.4 缓冲区问题补充为什么_exit不刷新缓冲区
exit属于是语言级别的在三号手册 而_exit是系统级别的在二号手册。
_exit本质上是系统调用
所以我们上面的_exit实际上是绕过了语言层 直接进行了系统调用 而刚刚的缓冲区是语言级别的 fflush也是语言级别的。
六、进程等待
首先我们可以查看一下fork的返回值 如果fork失败 则错误码会被设置。
6.1. wait和waitpid等待回收子进程
一般而言 父进程创建的子进程 父进程就要等待子进程进行回收 如果子进程一直不退出 则父进程就会阻塞在wait内部。
wait的作用等待任意的子进程参数可以传nullptr表示不获取status
常用 waitpid的作用第一个参数 pid0表示等待指定的一个进程pid -1表示等待任意一个子进程
看一下他们的返回值 如果等待成功则返回对应的子进程如果等待失败则返回-1.
举个栗子
等待任何一个子进程
当然 我也可以修改id的参数比如更换为刚刚子进程id这里就不展示了。
waitpid的第二个参数它会帮助父进程获取子进程的退出信息通过参数的方式给我们带出来。输出型参数
但是这里的退出信息却是256为什么不是1呢
其实status这个参数包含的信息并不只是退出码它的本质是一个位图 它的结构中前八位是退出状态有256种状态 低七位是终止信号 还有一个标志位。
如果想要提取退出状态则需要进行位运算如下
小问题 那我们可不可以不使用status来获取状态码而是用一个全局变量呢
不可以 因为进程具有独立性子进程修改父进程看不到会发生写时拷贝。
6.2 阻塞与非阻塞 waitpid的第三个参数就是关于阻塞等待与非阻塞等待
首先waitpid的返回值 如果0表示返回目标进程pid, 如果 0 等待成功但是子进程没有退出 0 等待失败.
参数如果传0表示阻塞等待 如果传WNOHANG表示非阻塞等待
举个栗子 总结
进程控制是操作系统中的一个重要主题主要涉及如何管理和调度进程以确保计算机系统的高效运行 本篇完下篇见