公司网站被抄袭,应用市场商店,python微信小程序开发教程,中国新闻社官方网站1. 进程概述
进程和程序的区别:程序和进程是两个不同的概念#xff0c;他们的状态#xff0c;占用的系统资源都是不同的。
程序#xff1a;就是磁盘上的可执行文件文件, 并且只占用磁盘上的空间#xff0c;是一个静态的概念。进程#xff1a;被执行之后的程序叫做进程他们的状态占用的系统资源都是不同的。
程序就是磁盘上的可执行文件文件, 并且只占用磁盘上的空间是一个静态的概念。进程被执行之后的程序叫做进程不占用磁盘空间需要消耗系统的内存CPU资源每个运行的进程的都对应一个属于自己的虚拟地址空间这是一个动态的概念。
1.1 并行和并发
CPU时间片
CPU时间片是多任务操作系统中每个进程被分配的执行时间段。通过调度算法操作系统确保每个进程有机会执行防止某个进程长时间占用CPU。时间片的大小影响系统响应和效率是一种权衡考虑。
并发和并行
并发 多个任务在同一时间段内交替执行它们可能不是同时进行的而是通过快速切换实现感觉上的同时进行。并发主要强调任务之间的相互独立和独立进展。并行 多个任务在同一时刻同时执行真正的同时性。这通常涉及到物理上的多个处理单元例如多核处理器或多台计算机。
简而言之并发是指多个任务交替执行而并行是指多个任务同时执行。并发通常用于处理大量的任务使系统更加响应而并行则是通过同时执行多个任务来提高整体的处理速度。
1.2 PCB
PCB - 进程控制块Processing Control BlockLinux内核的进程控制块本质上是一个叫做 task_struct的结构体。在这个结构体中记录了进程运行相关的一些信息
PCB 是进程存在的唯一标识这意味着一个进程的存在必然会有一个 PCB如果进程消失了那么 PCB 也会随之消失。
包含的信息: 进程id(pid), 进程的状态, 进程优先级, 进程对应的虚拟地址空间的信息等, PCB 的信息在进程切换时被保存和恢复以确保操作系统能够正确地管理和调度多个进程。
1.3 进程状态
进程一共有五种状态分别为**创建态就绪态运行态阻塞态(挂起态)退出态(终止态)**其中创建态和退出态维持的时间是非常短的稍纵即逝。
就绪状态Ready 进程已经准备好执行等待分配CPU时间。通常进程在等待操作系统的调度时处于就绪状态。运行状态Running 进程正在CPU上执行指令。在任何给定时刻只能有一个进程处于运行状态。阻塞状态Blocked 进程被阻塞无法执行。这可能是因为等待某个事件的发生例如等待输入/输出完成、等待资源的释放等。在阻塞状态中的进程会等待操作系统的通知以便重新进入就绪状态。 1.4 进程命令 ps 显示当前进程的快照。 ps aux # 显示所有用户的所有进程
ps -ef # 显示所有进程的详细信息top 实时显示系统中正在运行的进程的信息以及系统的整体性能。 topkill 终止一个进程。 kill PID # 使用进程IDPID终止进程9号信号SIGKILL的行为是无条件杀死进程想要杀死哪个进程就可以把这个信号发送给这个进程操作如下
# 无条件杀死进程, 进程ID通过 ps aux 可以查看
$ kill -9 进程ID
$ kill -SIGKILL 进程ID2. 进程创建
2.1 函数
Linux中进程ID为 pid_t 类型其本质是一个正整数
获取当前进程的进程ID(PID)
#include sys/types.h
#include unistd.h
pid_t getpid(void);获取当前父进程的进程ID(PPID)
#include sys/types.h
#include unistd.h
pid_t getppid(void);创建一个新的进程
#include unistd.h
pid_t fork(void);2.2 fork()函数
fork 函数是在Unix/Linux操作系统中用于创建新进程的系统调用之一。调用 fork 会创建一个与调用进程几乎完全相同的新进程这两个进程将在不同的内存空间中运行。
基本语法
#include unistd.hpid_t fork(void);返回值 在父进程中fork 返回新创建子进程的进程IDPID。在子进程中fork 返回0。如果出现错误返回-1。
工作原理
当 fork 被调用时它会复制调用进程的内存和资源文件描述符、环境变量等。在父进程和子进程中fork 返回不同的值这样程序可以根据返回值来确定自己是父进程还是子进程。子进程是父进程的副本它从 fork 返回的位置开始执行。
示例
#include stdio.h
#include unistd.hint main() {pid_t pid;pid fork();if (pid -1) {// 错误处理perror(fork);return 1;} else if (pid 0) {// 父进程printf(Parent process, child PID %d\n, pid);} else {// 子进程printf(Child process\n);}// 父子进程都会执行到这里printf(This is common code for both parent and child\n);return 0;
}这段代码调用 fork 创建一个新的进程。父进程得到子进程的PID而子进程得到0。接下来父子进程都会执行相同的代码但可以通过 if (pid 0) 判断来执行不同的逻辑。
需要注意的是fork 创建的新进程是父进程的副本但并不共享父进程和子进程之间的变量。如果在父子进程中都修改同一个变量它们互不影响。
3. 父子进程
3.1 子进程执行位置
父进程肯定是从main()函数开始运行的子进程是在父进程中调用fork()函数之后被创建, 子进程就从fork()之后开始向下执行代码。 3.2 循环创建子进程
我们可以只让父进程创建子进程如果是子进程不让其继续创建子进程因此只需要在程序中添加关于父子进程的判断即可。
// 需要在上边的程序中控制不让子进程, 再创建子进程即可
// process_loop.c
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.hint main()
{pid_t pid;// 在循环中创建子进程for(int i0; i3; i){pid fork();if(pid 0){// 不让子进程执行循环, 直接跳出break;}}printf(当前进程pid: %d\n, getpid());return 0;
}3.3 终端显示问题
执行上述代码后会出现以下显示问题, 回车后即可恢复 a.out 进程启动之后共创建了3个子进程其实 a.out 也是有父进程的就是当前的终端终端只能检测到 a.out 进程的状态a.out执行期间终端切换到后台a.out执行完毕之后终端切换回前台当终端切换到前之后a.out的子进程还没有执行完毕当子进程输出的信息就显示到终端命令提示符的后边了导致终端显示有问题但是此时终端是可以接收键盘输入的只是看起来不美观而已。想要解决这个问题需要让所有子进程退出之后再退出父进程比如在父进程代码中调用 sleep()
修改后的代码: 执行结果: 3.4 进程数数
当父进程创建一个子进程那么父子进程之间可以通过全局变量互动实现交替数数的功能吗不过不确定可以写一段测试代码
// number.c
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h// 定义全局变量
int number 10;int main()
{printf(创建子进程之前 number %d\n, number);pid_t pid fork();// 父子进程都会执行这一行printf(当前进程fork()的返回值: %d\n, pid);//如果是父进程if(pid 0){printf(我是父进程, pid %d, number %d\n, getpid(), number);printf(父进程的父进程(终端进程), pid %d\n, getppid());sleep(1);}else if(pid 0){// 子进程number 100;printf(我是子进程, pid %d, number %d\n, getpid(), number);printf(子进程的父进程, pid %d\n, getppid());}return 0;
}结果: 两个进程中是不能通过全局变量实现数据交互的因为每个进程都有自己的地址空间两个同名全局变量存储在不同的虚拟地址空间中二者没有任何关联性。如果要进行进程间通信需要使用管道共享内存本地套接字内存映射区消息队列等方式。
4. exexl 和 execlp函数
在项目开发过程中有时候有这种需求需要通过现在运行的进程启动磁盘上的另一个可执行程序也就是通过一个进程启动另一个进程这种情况下我们可以使用 exec族函数 也就是说 exec族函数并没有创建新进程的能力只是有大无畏的牺牲精神让起启动的新进程寄生到自己虚拟地址空间之内并挖空了自己的地址空间用户区把新启动的进程数据填充进去。 4.1 execl()
#include unistd.h
// 变参函数
int execl(const char *path, const char *arg, ...);参数:
path: 要启动的可执行程序的路径, 推荐使用绝对路径arg: ps aux 查看进程的时候, 启动的进程的名字, 可以随意指定, 一般和要启动的可执行程序名相同… : 要执行的命令需要的参数可以写多个最后以 NULL 结尾表示参数指定完了。
返回值如果这个函数执行成功, 没有返回值如果执行失败, 返回 -1
4.2 execlp()
该函数常用于执行已经设置了环境变量的可执行程序, 因此使用这个函数执行可执行程序不需要指定路径只需要指定出名字即可。
// p path
int execlp(const char *file, const char *arg, ...);参数:
file: 可执行程序的名字 在环境变量PATH中可执行程序可以不加路径没有在环境变量PATH中, 可执行程序需要指定绝对路径 arg: ps aux 查看进程的时候, 启动的进程的名字, 可以随意指定, 一般和要启动的可执行程序名相同… : 要执行的命令需要的参数可以写多个最后以 NULL 结尾表示参数指定完了。
返回值如果这个函数执行成功, 没有返回值如果执行失败, 返回 -1
4.3 函数的使用
一般不会在进程中直接调用这个函数, 因为这样的话这个进程的代码区代码会被替换, 我们一般在调用这些函数的时候都会先创建一个子进程在子进程中调用 exec 族函数子进程的用户区数据被替换掉开始执行新的程序中的代码逻辑但是父进程不受任何影响仍然可以继续正常工作。
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.hint main()
{// 创建子进程pid_t pid fork();// 在子进程中执行磁盘上的可执行程序if(pid 0){// 磁盘上的可执行程序 /bin/ps
#if 1execl(/bin/ps, title, aux, NULL);// 也可以这么写// execl(/bin/ps, title, a, u, x, NULL);
#elseexeclp(ps, title, aux, NULL);// 也可以这么写// execl(ps, title, a, u, x, NULL);
#endif// 如果成功当前子进程的代码区别 ps中的代码区代码替换// 下面的所有代码都不会执行// 如果函数调用失败了,才会继续执行下面的代码perror(execl);printf(\n);printf(\n);printf(\n);printf(\n);printf(\n);printf(\n);}else if(pid 0){printf(我是父进程.....\n);}return 0;
}5. 进程控制
进程控制主要是指进程的退出, 进程的回收和进程的特殊状态 孤儿进程和僵尸进程。
5.1 结束进程
如果想要直接退出某个进程可以在程序的任何位置调用exit()或者_exit()函数。函数的参数相当于退出码
exit 函数是用于终止程序的标准库函数它可在C和C中使用。当程序执行到 exit 函数时它会正常退出并返回一个状态码给操作系统。
基本语法
#include stdlib.hvoid exit(int status);status指定要返回给操作系统的状态码。通常0 表示正常退出非零值表示异常退出或错误状态。
示例
#include stdio.h
#include stdlib.hint main() {printf(Before exit function\n);// 使用 exit 函数退出程序返回状态码 0exit(0);// 以下代码不会执行printf(After exit function\n);return 0;
}在这个例子中当程序执行到 exit(0) 时程序将立即终止不会执行后面的代码。此时操作系统将收到状态码 0表示程序正常退出。
exit 函数的主要作用是确保程序在退出时执行一些清理工作例如关闭文件、释放动态分配的内存等。如果不使用 exit 而直接让 main 函数返回那么可能会导致一些清理工作无法完成。
5.2 孤儿进程
在操作系统中**孤儿进程是指其父进程先于它自己退出导致它成为孤立的进程。**当一个进程创建子进程而子进程的父进程在子进程退出之前就已经终止这个子进程就会变成孤儿进程。
孤儿进程通常会被 init 进程在现代系统中可能是 systemd 或其他类似的进程接管。init 进程(PID 1)会定期检查系统中是否存在孤儿进程并负责清理这些进程的资源防止它们变成僵尸进程。
以下是孤儿进程的主要特征和处理方式
没有父进程 孤儿进程的父进程已经退出因此它没有父进程。接管 通常由 init 进程或类似的进程接管。init 进程会通过 wait 或类似的机制来收集孤儿进程的退出状态确保它们正常终止。不影响系统正常运行 孤儿进程并不会影响系统的正常运行但它们可能会占用系统资源因此需要及时被清理。
下面是一个产生孤儿进程的简单示例使用 fork 创建子进程并在子进程中让它先于父进程退出
#include stdio.h
#include stdlib.h
#include unistd.hint main() {pid_t pid fork();if (pid -1) {perror(fork);return 1;} else if (pid 0) {// 父进程printf(Parent process (PID%d) is sleeping...\n, getpid());sleep(2);printf(Parent process exits.\n);} else {// 子进程printf(Child process (PID%d) exits.\n, getpid());exit(0);}return 0;
}在这个例子中子进程在创建后立即退出而父进程会休眠一段时间后退出。因此子进程成为了一个孤儿进程。
5.3 僵尸进程
在一个启动的进程中创建子进程这时候就有了父子两个进程父进程正常运行, 子进程先与父进程结束, 子进程无法释放自己的PCB资源, 需要父进程来做这个件事儿, 但是如果父进程也不管, 这时候子进程就变成了僵尸进程。
僵尸进程不能将它看成是一个正常的进程这个进程已经死亡了用户区资源已经被释放了只是还占用着一些内核资源PCB。
要处理僵尸进程父进程通常需要调用 wait 或 waitpid 等系统调用以获取子进程的终止状态。一旦父进程处理了子进程的终止状态该子进程的资源就会被释放它不再是僵尸进程。
以下是一个简单的示例演示了一个父进程创建子进程然后父进程休眠一段时间导致子进程成为僵尸进程
#include stdio.h
#include stdlib.h
#include unistd.hint main() {pid_t pid fork();if (pid -1) {perror(fork);return 1;} else if (pid 0) {// 父进程printf(Parent process (PID%d) is sleeping...\n, getpid());sleep(5); // 父进程休眠一段时间让子进程成为僵尸进程printf(Parent process exits.\n);} else {// 子进程printf(Child process (PID%d) exits.\n, getpid());exit(0);}return 0;
}消灭僵尸进程的方法是杀死这个僵尸进程的父进程这样僵尸进程的资源就被系统回收了。通过kill -9 僵尸进程PID的方式是不能消灭僵尸进程的这个命令只对活着的进程有效僵尸进程已经死了 5.4 进程回收
在父进程中进行子进程的资源回收回收方式有两种一种是阻塞方式wait()一种是非阻塞方式waitpid()。
5.4.1 wait
这是个阻塞函数**如果没有子进程退出, 函数会一直阻塞等待, 当检测到子进程退出了, 该函数阻塞解除回收子进程资源。font**这个函数被调用一次, 只能回收一个子进程的资源如果有多个子进程需要资源回收, 函数需要被调用多次。
基本语法
#include sys/types.h
#include sys/wait.hpid_t wait(int *status);status用于存储子进程终止状态的指针。可以为NULL表示不关心子进程的终止状态。 返回值如果成功返回被等待子进程的进程IDPID。如果调用出错返回-1并设置 errno 表示错误类型。
#include stdio.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include unistd.hint main() {pid_t pid fork();if (pid -1) {perror(fork);return 1;} else if (pid 0) {// 父进程printf(Parent process (PID%d) is waiting for the child process...\n, getpid());int status;pid_t terminated_child wait(status);if (terminated_child -1) {perror(wait);return 1;}if (WIFEXITED(status)) {printf(Child process (PID%d) exited with status %d.\n, terminated_child, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf(Child process (PID%d) terminated by signal %d.\n, terminated_child, WTERMSIG(status));}printf(Parent process exits.\n);} else {// 子进程printf(Child process (PID%d) exits.\n, getpid()); exit(0); // 子进程退出后, 上面的wait函数解除阻塞}return 0;
}5.4.2 waitpid
waitpid 函数是用于等待指定子进程终止并获取其终止状态的系统调用。与 wait 不同waitpid 允许指定等待的子进程从而可以避免等待所有子进程的终止。
#include sys/types.h
#include sys/wait.hpid_t waitpid(pid_t pid, int *status, int options);参数: pid: -1回收所有的子进程资源, 和wait()是一样的, 无差别回收并不是一次性就可以回收多个, 也是需要循环回收的大于0指定回收某一个进程的资源 pid是要回收的子进程的进程ID0回收当前进程组的所有子进程ID小于 -1pid 的绝对值代表进程组ID表示要回收这个进程组的所有子进程资源 status: NULL, 和wait的参数是一样的 options: 控制函数是阻塞还是非阻塞 0: 函数是行为是阻塞的 和wait一样WNOHANG: 函数是行为是非阻塞的
返回值:
如果函数是非阻塞的, 并且子进程还在运行, 返回0成功: 得到子进程的进程ID失败: -1 没有子进程资源可以回收了, 函数如果是阻塞的, 阻塞会解除, 直接返回-1回收子进程资源的时候出现了异常
阻塞回收的例子和wait基本一样, 举一个非阻塞回收的例子:
#include stdio.h
#include stdlib.h
#include sys/types.h
#include sys/wait.h
#include unistd.hint main() {pid_t pid fork();if (pid -1) {perror(fork);return 1;} else if (pid 0) {// 父进程printf(Parent process (PID%d) is waiting for the child process...\n, getpid());int status;int options WNOHANG; // 使用 WNOHANG 选项非阻塞等待pid_t terminated_child;do {terminated_child waitpid(pid, status, options);if (terminated_child -1) {perror(waitpid);return 1;}if (terminated_child 0) {// 子进程还没有终止printf(No child process has terminated yet. Sleeping for a while...\n);sleep(1);}} while (terminated_child 0);if (WIFEXITED(status)) {printf(Child process (PID%d) exited with status %d.\n, terminated_child, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf(Child process (PID%d) terminated by signal %d.\n, terminated_child, WTERMSIG(status));}printf(Parent process exits.\n);} else {// 子进程printf(Child process (PID%d) exits.\n, getpid());exit(0);}return 0;
}