[英]Closing unused pipe file descriptors
I try to comprehend the basic reason for why closing file descriptors is needed. 我试图理解为什么需要关闭文件描述符的基本原因。 I get the reason for reader side closing write descriptor. 我知道读者端关闭写描述符的原因。 However, conversely, I can't see(simulate) in action the reason for writing side closing read descriptor. 但是,相反地,我无法看到(模拟)编写侧关闭读取描述符的原因。 I try to apply following one, 我尝试以下一项,
When a process tries to write to a pipe for which no process has an open read descriptor, the kernel sends the
SIGPIPE
signal to the writing process. 当某个进程尝试写入没有进程具有打开的读取描述符的管道时,内核会将SIGPIPE
信号发送到写入进程。 By default, this signal kills a process. 默认情况下,此信号会终止进程。Source, The Linux programming interface, Michael Kerrisk 来源,Linux编程接口,Michael Kerrisk
write()
, on error,-1
is returned, and errno is set appropriately. 错误时返回write()
,返回-1
,并正确设置errno。EPIPE
fd is connected to a pipe or socket whose reading end is closed.EPIPE
fd连接到读数端关闭的管道或插座。 When this happens the writing process will also receive aSIGPIPE
signal. 发生这种情况时,写入过程还将收到SIGPIPE
信号。 (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.) (因此,仅当程序捕获,阻止或忽略此信号时,才能看到写返回值。)Source, man pages. 来源,手册页。
To do that, I close already read descriptor before fork()
. 为此,我在fork()
之前关闭已读取的描述符。 Nevertheless, neither I can catch SIGPIPE
, nor print error of write()
by perror()
. 但是,我既无法捕获SIGPIPE
,也无法通过perror()
打印write()
的错误。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <signal.h>
#define BUFSIZE 100
char const * errMsgPipe = "signal handled SIGPIPE\n";
int errMsgPipeLen;
void handler(int x) {
write(2, errMsgPipe, errMsgPipeLen);
}
int main(void) {
errMsgPipeLen = strlen(errMsgPipe);
char bufin[BUFSIZE] = "empty";
char bufout[] = "hello soner";
int bytesin;
pid_t childpid;
int fd[2];
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = 0;
sigfillset(&sa.sa_mask);
sa.sa_handler = handler;
sigaction(SIGPIPE, &sa, 0);
if (pipe(fd) == -1) {
perror("Failed to create the pipe");
return 1;
}
bytesin = strlen(bufin);
childpid = fork();
if (childpid == -1) {
perror("Failed to fork");
return 1;
}
close(fd[0]);
if (childpid) {
if (write(fd[1], bufout, strlen(bufout)+1) < 0) {
perror("write");
}
}
else
bytesin = read(fd[0], bufin, BUFSIZE);
fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",
(long)getpid(), bytesin, bufin, bufout);
return 0;
}
Output: 输出:
[22686]:my bufin is {empty}, my bufout is {hello soner}
[22687]:my bufin is {empty}, my bufout is {hello soner}
Expected output: 预期产量:
[22686]:my bufin is {empty}, my bufout is {hello soner}
signal handled SIGPIPE or similar stuff
Here is a scenario where closing the read end of a pipe matters: 在以下情况下,关闭管道的读取端很重要:
seq 65536 | sed 10q
If the process that launches seq
does not close the read end of the pipe, then seq
will fill the pipe buffer (it would like to write 382,110 bytes, but the pipe buffer isn't that big) but because there is a process with the read end of the pipe open ( seq
), it will not get SIGPIPE or a write error, so it will never complete. 如果启动seq
的进程没有关闭管道的读取端,则seq
将填充管道缓冲区(它想写入382,110字节,但管道缓冲区不是那么大),但是因为存在一个带有读取管道打开端( seq
)的末尾,它将不会获得SIGPIPE或写入错误,因此它将永远不会完成。
Consider this code. 考虑下面的代码。 The program runs seq 65536 | sed 10q
该程序运行seq 65536 | sed 10q
seq 65536 | sed 10q
, but depending on whether it is invoked with any arguments or not, it does or does not close the read end of the pipe to the seq
program. seq 65536 | sed 10q
,但是根据是否使用任何参数调用它,它是否关闭对seq
程序的管道的读取端。 When it is run without arguments, the seq
program never gets SIGPIPE or a write error on its standard output because there is a process with the read end of the pipe open — that process is seq
itself. 如果在不带参数的情况下运行seq
程序,则它的标准输出永远不会获得SIGPIPE或写入错误,因为存在一个进程,该进程的管道的读取端处于打开状态–该进程本身就是seq
。
#include "stderr.h"
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int fd[2];
int pid1;
int pid2;
if (pipe(fd) != 0)
err_syserr("failed to pipe: ");
if ((pid1 = fork()) < 0)
err_syserr("failed to fork 1: ");
else if (pid1 == 0)
{
char *sed[] = { "sed", "10q", 0 };
if (dup2(fd[0], STDIN_FILENO) < 0)
err_syserr("failed to dup2 read end of pipe to standard input: ");
close(fd[0]);
close(fd[1]);
execvp(sed[0], sed);
err_syserr("failed to exec %s: ", sed[0]);
}
else if ((pid2 = fork()) < 0)
err_syserr("failed to fork 2: ");
else if (pid2 == 0)
{
char *seq[] = { "seq", "65536", 0 };
if (dup2(fd[1], STDOUT_FILENO) < 0)
err_syserr("failed to dup2 write end of pipe to standard output: ");
close(fd[1]);
if (argc > 1)
close(fd[0]);
execvp(seq[0], seq);
err_syserr("failed to exec %s: ", seq[0]);
}
else
{
int corpse;
int status;
close(fd[0]);
close(fd[1]);
printf("read end of pipe is%s closed for seq\n", (argc > 1) ? "" : " not");
printf("shell process is PID %d\n", (int)getpid());
printf("sed launched as PID %d\n", pid1);
printf("seq launched as PID %d\n", pid2);
while ((corpse = wait(&status)) > 0)
printf("%d exited with status 0x%.4X\n", corpse, status);
printf("shell process is exiting\n");
}
}
The library code is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory. 库代码在GitHub上的我的SOQ (堆栈溢出问题)存储库中以src / libsoq子目录中的stderr.c
和stderr.h
文件的stderr.c
。
Here's a pair of sample runs (the program was called fork29
): 这是一对示例运行(该程序称为fork29
):
$ fork29
read end of pipe is not closed for seq
shell process is PID 90937
sed launched as PID 90938
seq launched as PID 90939
1
2
3
4
5
6
7
8
9
10
90938 exited with status 0x0000
^C
$ fork29 close
read end of pipe is closed for seq
shell process is PID 90940
sed launched as PID 90941
seq launched as PID 90942
1
2
3
4
5
6
7
8
9
10
90941 exited with status 0x0000
90942 exited with status 0x000D
shell process is exiting
$
Note that the exit status of seq
in the second example indicates that it died from signal 13, SIGPIPE. 请注意,第二个示例中seq
的退出状态表明它已死于信号13 SIGPIPE。
(1) How are we sure that here
seq
executes beforesed
? (1)我们如何确定seq
在sed
之前执行? How is there no race? 怎么没有种族?
The two programs ( seq
and sed
) execute concurrently. 这两个程序( seq
和sed
)同时执行。 sed
cannot read anything until seq
has produced it. 在seq
生成之前, sed
无法读取任何内容。 seq
might fill the pipe before sed
reads anything, or it might only fill it after sed
has quit. seq
可能在sed
读取任何内容之前就填充了管道,或者可能仅在sed
退出之后才填充了管道。
(2) Why do we close both
fd[0]
andfd[1]
insed
? (2)为什么在sed
同时关闭fd[0]
和fd[1]
? Why not onlyfd[1]
? 为什么不仅是fd[1]
? Similar forseq
.seq
类似。
Rule of thumb : If you dup2()
one end of a pipe to standard input or standard output, close both of the original file descriptors returned by pipe()
as soon as possible. 经验法则 :如果将管道的一端dup2()
为标准输入或标准输出,请尽快关闭pipe()
返回的两个原始文件描述符。 In particular, you should close them before using any of the exec*()
family of functions. 特别是,在使用任何exec*()
系列函数之前,应关闭它们。
The rule also applies if you duplicate the descriptors with either dup()
or fcntl()
with F_DUPFD
如果您使用带有F_DUPFD
dup()
或fcntl()
复制描述符,则该规则也适用
The code for sed
follows the Rule of Thumb. sed
的代码遵循经验法则。 The code for seq
only does so conditionally, so you can see what happens when you don't follow the Rule of Thumb. seq
的代码仅在有条件的情况下执行,因此您可以看到不遵循经验法则时会发生什么。
Here is a scenario where closing the write end of a pipe matters: 在以下情况下,关闭管道的写端很重要:
ls -l | sort
If the process that launches sort
does not close the write end of the pipe, then sort
could write to the pipe, so it will never see EOF on the pipe, so it will never complete. 如果启动sort
的进程没有关闭管道的写入端,则sort
可能会写入管道,因此它将永远不会在管道上看到EOF,因此它将永远不会完成。
Consider this code. 考虑下面的代码。 The program runs ls -l | sort
该程序运行ls -l | sort
ls -l | sort
, but depending on whether it is invoked with any arguments or not, it does or does not close the write end of the pipe to the sort
program. ls -l | sort
,但是取决于是否使用任何参数调用它,它是否关闭对sort
程序的管道的写端。 When it is run without arguments, then, the sort
program never sees EOF on its standard input because there is a process with the write end of the pipe open — that process is sort
itself. 当它不带参数运行时, sort
程序就永远不会在其标准输入上看到EOF,因为有一个进程的管道的写端处于打开状态–该进程本身就是sort
。
#include "stderr.h"
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int fd[2];
int pid1;
int pid2;
if (pipe(fd) != 0)
err_syserr("failed to pipe: ");
if ((pid1 = fork()) < 0)
err_syserr("failed to fork 1: ");
else if (pid1 == 0)
{
char *sort[] = { "sort", 0 };
if (dup2(fd[0], STDIN_FILENO) < 0)
err_syserr("failed to dup2 read end of pipe to standard input: ");
close(fd[0]);
if (argc > 1)
close(fd[1]);
execvp(sort[0], sort);
err_syserr("failed to exec %s: ", sort[0]);
}
else if ((pid2 = fork()) < 0)
err_syserr("failed to fork 2: ");
else if (pid2 == 0)
{
char *ls[] = { "ls", "-l", 0 };
if (dup2(fd[1], STDOUT_FILENO) < 0)
err_syserr("failed to dup2 write end of pipe to standard output: ");
close(fd[1]);
close(fd[0]);
execvp(ls[0], ls);
err_syserr("failed to exec %s: ", ls[0]);
}
else
{
int corpse;
int status;
close(fd[0]);
close(fd[1]);
printf("write end of pipe is%s closed for sort\n", (argc > 1) ? "" : " not");
printf("shell process is PID %d\n", (int)getpid());
printf("sort launched as PID %d\n", pid1);
printf("ls launched as PID %d\n", pid2);
while ((corpse = wait(&status)) > 0)
printf("%d exited with status 0x%.4X\n", corpse, status);
printf("shell process is exiting\n");
}
}
Here's a pair of sample runs (the program was called fork13
): 这是一对示例运行(该程序称为fork13
):
$ fork13
write end of pipe is not closed for sort
shell process is PID 90737
sort launched as PID 90738
ls launched as PID 90739
90739 exited with status 0x0000
^C
$ fork13 close
write end of pipe is closed for sort
shell process is PID 90741
sort launched as PID 90742
ls launched as PID 90743
90743 exited with status 0x0000
-rw-r--r-- 1 jleffler staff 1583 Jun 23 14:20 fork13.c
-rwxr-xr-x 1 jleffler staff 22216 Jun 23 14:20 fork13
drwxr-xr-x 3 jleffler staff 96 Jun 23 14:06 fork13.dSYM
total 56
90742 exited with status 0x0000
shell process is exiting
$
(3) Why do we need to close both
fd[0]
andfd[1]
in their parent? (3)为什么我们需要同时关闭它们的父级中的fd[0]
和fd[1]
?
The parent process isn't actively using the pipe it created. 父进程未积极使用其创建的管道。 It must close it fully, otherwise the other programs won't end. 它必须完全关闭它,否则其他程序将不会结束。 Try it — I did (unintentionally) and the programs didn't behave as I intended (expected). 尝试一下-我(无意间)做了,并且程序没有达到我的预期(预期)。 It took me a couple of seconds to realize what I'd not done! 我花了几秒钟才意识到我还没做完!
snr posted an 'answer' attempting to demonstrate signal handling and what happens with closing (or not) the read end of pipe file descriptors. snr发布了一个“答案”,试图演示信号处理以及关闭(或不关闭)管道文件描述符的读取端时发生的情况。 Here's an adaptation of that code into a program that can be controlled with command line options, where permutations of the options can yield different and useful results. 这是将该代码改编为可以通过命令行选项控制的程序,其中选项的排列可以产生不同且有用的结果。 The -b
and -a
options allow you to close the read end of the pipe before or after the fork (or not close it at all). -b
和-a
选项允许您在派生叉之前或之后关闭管道的读取端(或完全不关闭)。 The -h
and -i
allow you to handle SIGPIPE with the signal handler or ignore it (or use the default handling — terminate). -h
和-i
允许您使用信号处理程序来处理SIGPIPE或将其忽略(或使用默认处理-终止)。 And the -d
option allows you to delay the parent by 1 second before it attempts to write. -d
选项使您可以在父级尝试写入之前将其延迟1秒。
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "stderr.h"
#define BUFSIZE 100
static char const *errMsgPipe = "signal handled SIGPIPE\n";
static int errMsgPipeLen;
static void handler(int x)
{
if (x == SIGPIPE)
write(2, errMsgPipe, errMsgPipeLen);
}
static inline void print_bool(const char *tag, bool value)
{
printf(" %5s: %s\n", (value) ? "true" : "false", tag);
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
bool sig_ignore = false;
bool sig_handle = false;
bool after_fork = false;
bool before_fork = false;
bool parent_doze = false;
static const char usestr[] = "[-abdhi]";
int opt;
while ((opt = getopt(argc, argv, "abdhi")) != -1)
{
switch (opt)
{
case 'a':
after_fork = true;
break;
case 'b':
before_fork = true;
break;
case 'd':
parent_doze = true;
break;
case 'h':
sig_handle = true;
break;
case 'i':
sig_ignore = true;
break;
default:
err_usage(usestr);
}
}
if (optind != argc)
err_usage(usestr);
/* Both these happen naturally - but should be explicit when printing configuration */
if (sig_handle && sig_ignore)
sig_ignore = false;
if (before_fork && after_fork)
after_fork = false;
printf("Configuration:\n");
print_bool("Close read fd before fork", before_fork);
print_bool("Close read fd after fork", after_fork);
print_bool("SIGPIPE handled", sig_handle);
print_bool("SIGPIPE ignored", sig_ignore);
print_bool("Parent doze", parent_doze);
err_setlogopts(ERR_PID);
errMsgPipeLen = strlen(errMsgPipe);
char bufin[BUFSIZE] = "empty";
char bufout[] = "hello soner";
int bytesin;
pid_t childpid;
int fd[2];
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = 0;
sigfillset(&sa.sa_mask);
sa.sa_handler = SIG_DFL;
if (sig_ignore)
sa.sa_handler = SIG_IGN;
if (sig_handle)
sa.sa_handler = handler;
if (sigaction(SIGPIPE, &sa, 0) != 0)
err_syserr("sigaction(SIGPIPE) failed: ");
printf("Parent: %d\n", (int)getpid());
if (pipe(fd) == -1)
err_syserr("pipe failed: ");
if (before_fork)
close(fd[0]);
int val = -999;
bytesin = strlen(bufin);
childpid = fork();
if (childpid == -1)
err_syserr("fork failed: ");
if (after_fork)
close(fd[0]);
if (childpid)
{
if (parent_doze)
sleep(1);
val = write(fd[1], bufout, strlen(bufout) + 1);
if (val < 0)
err_syserr("write to pipe failed: ");
err_remark("Parent wrote %d bytes to pipe\n", val);
}
else
{
bytesin = read(fd[0], bufin, BUFSIZE);
if (bytesin < 0)
err_syserr("read from pipe failed: ");
err_remark("Child read %d bytes from pipe\n", bytesin);
}
fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",
(long)getpid(), bytesin, bufin, bufout);
return 0;
}
It can be difficult (non-obvious, at any rate) to track what happens to the parent process. 跟踪父进程发生的事情可能很困难(很明显)。 Bash generates an exit status of 128 + signal number when a child dies from a signal. 当孩子从信号中死亡时,Bash会生成退出状态128 +信号编号。 On this machine, SIGPIPE is 13, so an exit status of 141 indicates death from SIGPIPE. 在这台机器上,SIGPIPE为13,因此退出状态为141表示已从SIGPIPE中退出。
Example runs: 示例运行:
$ pipe71; echo $?
Configuration:
false: Close read fd before fork
false: Close read fd after fork
false: SIGPIPE handled
false: SIGPIPE ignored
false: Parent doze
Parent: 97984
pipe71: pid=97984: Parent wrote 12 bytes to pipe
[97984]:my bufin is {empty}, my bufout is {hello soner}
pipe71: pid=97985: Child read 12 bytes from pipe
[97985]:my bufin is {hello soner}, my bufout is {hello soner}
0
$ pipe71 -b; echo $?
Configuration:
true: Close read fd before fork
false: Close read fd after fork
false: SIGPIPE handled
false: SIGPIPE ignored
false: Parent doze
Parent: 97987
pipe71: pid=97988: read from pipe failed: error (9) Bad file descriptor
141
$ pipe71 -a; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
false: SIGPIPE handled
false: SIGPIPE ignored
false: Parent doze
Parent: 98000
pipe71: pid=98000: Parent wrote 12 bytes to pipe
[98000]:my bufin is {empty}, my bufout is {hello soner}
0
pipe71: pid=98001: read from pipe failed: error (9) Bad file descriptor
$ pipe71 -a -d; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
false: SIGPIPE handled
false: SIGPIPE ignored
true: Parent doze
Parent: 98004
pipe71: pid=98005: read from pipe failed: error (9) Bad file descriptor
141
$ pipe71 -h -a -d; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
true: SIGPIPE handled
false: SIGPIPE ignored
true: Parent doze
Parent: 98007
pipe71: pid=98008: read from pipe failed: error (9) Bad file descriptor
signal handled SIGPIPE
pipe71: pid=98007: write to pipe failed: error (32) Broken pipe
1
$ pipe71 -h -a; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
true: SIGPIPE handled
false: SIGPIPE ignored
false: Parent doze
Parent: 98009
pipe71: pid=98009: Parent wrote 12 bytes to pipe
[98009]:my bufin is {empty}, my bufout is {hello soner}
pipe71: pid=98010: read from pipe failed: error (9) Bad file descriptor
0
$ pipe71 -i -a; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
false: SIGPIPE handled
true: SIGPIPE ignored
false: Parent doze
Parent: 98013
pipe71: pid=98013: Parent wrote 12 bytes to pipe
[98013]:my bufin is {empty}, my bufout is {hello soner}
0
pipe71: pid=98014: read from pipe failed: error (9) Bad file descriptor
$ pipe71 -d -i -a; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
false: SIGPIPE handled
true: SIGPIPE ignored
true: Parent doze
Parent: 98015
pipe71: pid=98016: read from pipe failed: error (9) Bad file descriptor
pipe71: pid=98015: write to pipe failed: error (32) Broken pipe
1
$ pipe71 -i -a; echo $?
Configuration:
false: Close read fd before fork
true: Close read fd after fork
false: SIGPIPE handled
true: SIGPIPE ignored
false: Parent doze
Parent: 98020
pipe71: pid=98020: Parent wrote 12 bytes to pipe
[98020]:my bufin is {empty}, my bufout is {hello soner}
0
pipe71: pid=98021: read from pipe failed: error (9) Bad file descriptor
$
On my machine (a MacBook Pro running macOS High Sierra 10.13.5, with GCC 8.1.0), if I do not delay the parent, the parent consistently writes to the pipe before the child gets around to closing the file descriptor. 在我的机器上(运行macOS High Sierra 10.13.5且运行GCC 8.1.0的MacBook Pro),如果我不延迟父级,则父级会在子级关闭文件描述符之前始终写入管道。 That is not, however, guaranteed behaviour. 但是,这不能保证行为。 It would be possible to add another option (eg -n
for child_nap
) to make the child nap for a second. 可以添加另一个选项(例如, -n
表示child_nap
)以使子级小睡一秒钟。
The code for the programs shown above ( fork29.c
, fork13.c
, pipe71.c
) are available in my SOQ (Stack Overflow Questions) repository on GitHub as files fork13.c
, fork29.c
, pipe71.c
in the src/so-5100-4470 sub-directory. 上面显示的程序代码( fork29.c
, fork13.c
, pipe71.c
)在GitHub上的我的SOQ (堆栈溢出问题)存储库中以src /中的fork13.c
, fork29.c
, pipe71.c
文件fork13.c
。 so-5100-4470子目录。
My problem is related to place of close(fd[0]);
我的问题与close(fd[0]);
位置有关close(fd[0]);
I comment out its reason in the code. 我在代码中注释掉了它的原因。 Now, I get the error expected. 现在,我得到了预期的错误。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <signal.h>
#include <errno.h>
#define BUFSIZE 100
char const * errMsgPipe = "signal handled SIGPIPE\n";
int errMsgPipeLen;
void handler(int x) {
write(2, errMsgPipe, errMsgPipeLen);
}
int main(void) {
errMsgPipeLen = strlen(errMsgPipe);
char bufin[BUFSIZE] = "empty";
char bufout[] = "hello soner";
int bytesin;
pid_t childpid;
int fd[2];
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = 0;
sigfillset(&sa.sa_mask);
sa.sa_handler = handler;
sigaction(SIGPIPE, &sa, 0);
if (pipe(fd) == -1) {
perror("Failed to create the pipe");
return 1;
}
close(fd[0]); // <-- it's in order for no process has an open read descriptor
int val = -999;
bytesin = strlen(bufin);
childpid = fork();
if (childpid == -1) {
perror("Failed to fork");
return 1;
}
/*
* close(fd[0]); <---- if it were here, we wouldn't get expected error and signal
* since, parent can be reached to write(fd[1], .... ) call
* before the child close(fd[0]); call defined here it.
* It means there is by child open read descriptor seen by parent.
*/
// sleep(1); <---- we can prove my saying by calling sleep() here
if (childpid) {
val = write(fd[1], bufout, strlen(bufout)+1);
if (val < 0) {
perror("writing process error");
}
}
else {
bytesin = read(fd[0], bufin, BUFSIZE);
}
fprintf(stderr, "[%ld]:my bufin is {%.*s}, my bufout is {%s}\n",
(long)getpid(), bytesin, bufin, bufout);
return 0;
}
Output: 输出:
signal handled SIGPIPE
writing process error: Broken pipe
[27289]:my bufin is {empty}, my bufout is {hello soner}
[27290]:my bufin is {empty}, my bufout is {hello soner}
Therewithal, the saying that if the parent's write operation fails, the child's bufin
contains empty
is verified. 因此,可以说, 如果父母的写操作失败,则孩子的bufin
包含empty
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.