Linux多进程学习笔记(一)



一、进程终止方式

有8种方式使进程终止(termination),其中5种为正常终止,它们是:

(1)从main 返回;
(2)调用 exit;
(3)调用_exit或_Exit;
(4)最后一个线程从其启动例程返回;
(5)从最后一个线程调用pthread_exit。
异常终止有3种方式,它们是:
(6)调用abort;
(7)接到一个信号;
(8)最后一个线程对取消请求做出响应。

在这里插入图片描述
在 C 语言中,exit、_exit 和 _Exit 都是用来终止当前进程的函数,但它们在终止进程时的行为有所不同。

exit

  • exit 是标准 C 库函数,定义在 <stdlib.h> 中。
  • 它是正常的退出方式,会调用所有注册的退出处理程序(通过 atexit 或 on_exit 注册)。
  • 它会刷新所有打开的文件流(如 stdout、stderr)。
  • 它会关闭所有文件描述符。
  • 它会释放分配的资源,如内存。
  • exit 会以返回状态码的形式向操作系统报告退出状态。

函数原型:

void exit(int status);

status:退出状态码,通常 0 表示成功,非零表示错误。

_exit

  • _exit 是一个低级别的函数,通常定义在 <unistd.h> 中。
  • 它不会调用退出处理程序,也不会刷新文件流或关闭文件描述符。
  • 它直接终止进程,不进行任何清理工作。
  • _exit 通常用于紧急情况下的进程终止。

函数原型:

void _exit(int status);

status:退出状态码。

_Exit

  • _Exit 是一个介于 exit 和 _exit 之间的函数。
  • 它会调用所有注册的退出处理程序,刷新所有打开的文件流,并关闭所有文件描述符。
  • 但是,它不会释放动态分配的内存,这需要程序员手动管理。

函数原型:

void _Exit(int status);

status:退出状态码。

总结

  • exit 是最常用的退出函数,因为它会执行所有的清理工作。
  • _exit 用于需要立即终止进程且不需要任何清理的情况。
  • _Exit 用于需要执行退出处理程序但不需要释放动态分配内存的情况。
  • 在使用这些函数时,应该根据程序的具体需求和上下文来选择合适的退出方式。

二、进程的产生

1.fork()

1.注意事项

#include <unistd.h>
pid_t fork(void);

fork() 返回两次:在父进程中返回子进程的进程ID(一个正整数),在子进程中返回0。
如果发生错误,则在父进程中返回 -1,并设置 errno 以指示错误。

将子进程ID 返回给父进程的理由是:
因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程D。
fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,
所以子进程总是可以调用getppid以获得其父进程的进程ID(进程ID0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)。

fork() 是一个非常重要的系统调用,用于创建一个新的进程,称为子进程。子进程是调用进程(父进程)的副本,它继承了父进程的大部分资源,包括内存、文件描述符和变量等但不与父进程共享这些资源。

  • 注意理解关键字:duplicating,意味着拷贝,克隆,一模一样等含义
  • fork后父子进程的区别:fork的返回值不一样,pid不同,ppid也不同,未决信号和文件锁不继承,资源利用量清0
  • init进程:1号,是所有进程的祖先进程
  • 不能凭空猜测子进程或父进程先运行,调度器的调度策略来决定那个进程先运行
  • fflush()的重要性

在fork之前应该刷新所有该刷新的流,这能避免在实际生产过程中的大部分问题
在这里插入图片描述

  • 在重定向父进程的标准输出时,子进程的标准输出也被重定向。
  • 只使用fork()产生而不进行回收的子进程会成为僵尸进程,占用PID资源
  • fork()产生的子进程在父进程消亡后会由init进程接管

2.文件共享

实际上,fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。
父进程和子进程每个相同的打开描述符共享一个文件表项

考虑下述情况,一个进程具有3个不同的打开文件,它们是标准输入、标准输出和标准错误。
在从 fork 返回时,我们有了如图所示的结构。重要的一点是,父进程和子进程共享同一个文件偏移量。考虑下述情况:一个进程fork了一个子进程,然后等待子进程终止。假定,作为普通处理的一部分,父进程和子进程都向标准输出进行写操作。如果父进程的标准输出已重定向(很可能是由shell实现的),那么子进程写到该标准输出时,它将更新与父进程共享的该文件的偏移量。在这个例子中,当父进程等待子进程时,子进程写到标准输出:而在子进程终止后,父进程也写到标准输出上,并且知道其输出会追加在子进程所写数据之后。如果父进程和子进程不共享同一文件偏移量要实现这种形式的交互就要困难得多,可能需要父进程显式地动作。
在这里插入图片描述

2.init进程和vfork()

1.vfork()

vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不会引用该地址空间。不过在子进程调用exec或exit之前它在父进程的空间中运行。这种优化工作方式在某些UNIX系统的实现中提高了效率,但如果子进程修改数据(除了用于存放vfork返回值的变量)、进行函数调用、或者没有调用exec或exit就返回都可能会带来未知的结果。(实现采用写时复制技术以提高fork之后跟随exec操作的效率,但是不复制比部分复制还是要快一些。)
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。)

2.init进程

在 Linux 操作系统中,init 进程是一个非常重要的进程,它是系统启动后的第一个进程,其进程 ID (PID) 通常为 1。init 进程负责启动系统的其他部分,包括运行系统和服务的脚本、初始化用户空间环境等。

init 进程的作用包括:
系统初始化:在系统启动时,init 进程是第一个运行的进程,它负责执行系统的初始化脚本,设置必要的环境变量,挂载文件系统等。
运行级别管理:init 进程根据系统的运行级别(runlevel)来启动相应的服务。不同的运行级别对应不同的系统状态,例如,多用户模式、单用户模式、图形界面模式等。
服务管理:在系统运行过程中,init 进程负责管理和监督其他服务进程,确保它们正常运行。如果某个服务崩溃,init 进程可以重新启动该服务。
会话管理:init 进程还负责创建用户会话,当用户登录系统时,init 进程会为用户创建一个新的会话,并启动用户的 shell 或其他指定的程序。
系统关机和重启:当执行关机或重启命令时,init 进程会通知所有运行中的服务和进程,让它们优雅地关闭,然后执行关机或重启操作。

init进程完成从内核态向用户态的转变

在这里插入图片描述
而后启动其他用户进程

init进程刚开始运行的时候是内核态,它属于一个内核线程,然后运行一个用户态下面的程序后,把自己强行转成用户态(后面的进程需要工作在用户态下)。
init进程完成了从内核态到用户态的过渡,因此后续的其他进程都可以工作在用户态。

3.进程的消亡及释放资源

当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。对于这种信号的系统默认动作是忽略它。现在需要知道的是调用 wait 或 waitpid 的进程可能会发生什么。

  • 如果其所有子进程都还在运行,则阻塞。
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  • 如果它没有任何子进程,则立即出错返回。

如果子进程已经终止,并且是一个僵死进程,则wait立即返回并取得该子进程的状态;否则 wait 使其调用者阻塞,直到一个子进程终止。如调用者阻塞而且它有多个子进程,则在其某一子进程终止时,wait 就立即返回。因为 wait 返回终止子进程的进程ID,所以它总能了解是哪一个子进程终止了。

如果进程由于接收到 SIGCHLD信号而调用 wait,我们期望 wait 会立即返回。但是如果在随机时间点调用 wait,则进程可能会阻塞。

wait()与waitpid()

#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
两个函数返回值:若成功,返回进程ID;若出错,返回0(见后面的说明)-1
  • 在一个子进程终止前,wait 使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞。
  • waitpid 并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。

对于 waitpid 函数中 pid 参数的作用解释如下。
pid == -1 等待任一子进程。此种情况下,waitpid与wait等效
pid > 0 等待进程ID与 pid 相等的子进程
pid == 0 等待组 ID 等于调用进程组 ID的任一子进程
pid <- 1 等待组 ID 等于 pid 绝对值的任一子进程
waitpid函数返回终止子进程的进程ID,并将该子进程的终止状态存放在由starloc 指向的存储单元中。对于wait,其唯一的出错是调用进程没有子进程。但是对于waitpid,如果指定的进程或进程组不存在,或者参数 pid 指定的进程不是调用进程的子进程,都可能出错。
在这里插入图片描述
waitpid 函数提供了 wait 函数没有提供的3个功能。
(1)waitpid 可等待一个特定的进程,而 wait 则返回任一终止子进程的状态。在讨论 popen函数时会再说明这一功能。
(2)waitpid提供了一个wait的非阻塞版本。有时希望获取一个子进程的状态,但不想阻塞。
(3)waitpid通过WUNTRACED和WCONTINUED选项支持作业控制。

如果子进程已经终止,并且是一个僵死进程,则wait立即返回并取得该子进程的状态;否则 wait 使其调用者阻塞,直到一个子进程终止。如调用者阻塞而且它有多个子进程,则在其某一子进程终止时,wait就立即返回。因为wait 返回终止子进程的进程 ID,所以它总能了解是哪一个子进程终止了。

参数 statloc 是一个整型指针。如果 statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。

待更新...

总结

待写:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值