前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >日志录入之旅:深入了解程序日志的编写和执行

日志录入之旅:深入了解程序日志的编写和执行

原创
作者头像
Lion Long
发布2024-10-30 22:21:59
940
发布2024-10-30 22:21:59
举报
文章被收录于专栏:后端开发技术

一、相关IO接口函数

1.1、fwrite()和fread()

函数原型:

代码语言:javascript
复制
#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

描述: 函数fread()从stream指向的流中读取nmemb数据项,每个数据项的长度为size字节,并将它们存储在ptr指定的位置。

函数fwrite()将nmemb数据项写入stream指向的流,每个数据项的长度为size字节,并从ptr给定的位置获取它们。

有关非锁定副本,请参见unlocked_stdio()。

返回值: 成功时,fread()和fwrite()返回读取或写入的项数。此数字等于仅当size为1时传输的字节数。如果发生错误或到达文件结尾,则返回值为短项目计数(或零)。

fread()无法区分文件结尾和错误,调用方必须使用feof()和ferror()来确定发生了哪一个错误。

1.2、fclose()

关闭流,函数原型为:

代码语言:javascript
复制
#include <stdio.h>

int fclose(FILE *stream);

描述: 刷新stream指向的流(使用fflush()写入任何缓冲的输出数据),并关闭底层文件描述符。

如果stream参数是非法指针,或者是已经传递给前一次调用fclose()的描述符,则fclose()的行为是未定义的。

返回值: 成功完成后返回0。否则,将返回EOF,并设置errno以指示错误。无论哪种情况,对流的任何进一步访问(包括对fclose()的另一个调用)都会导致未定义的行为。

错误

EBADF:stream底层的文件描述符无效。

fclose()函数也可能失败,并为例程close()、write()或fflush()指定的任何错误设置errno。

注意: 请注意,fclose()只刷新C库提供的用户空间缓冲区。为了确保数据物理存储在磁盘上,还必须刷新内核缓冲区,例如,使用sync()或fsync()。

1.3、fflush()

刷新一个流,函数原型:

代码语言:javascript
复制
#include <stdio.h>

int fflush(FILE *stream);

描述: 对于输出流,fflush()通过流的底层写函数强制写入给定输出或更新stream的所有用户空间缓冲数据。

对于与可查找文件(例如,磁盘文件,但不是管道或终端)关联的输入流,fflush()将丢弃从基础文件提取但应用程序尚未使用的任何缓冲数据。

流的打开状态不受影响。

如果stream参数为NULL,则fflush()将刷新所有打开的输出流。

有关非锁定副本,请参见unlocked_stdio()。

返回值:

成功完成后返回0。否则,将返回EOF,并设置errno以指示错误。

错误:

EBADF:stream不是开放流,或者不开放用于写入。

函数fflush()也可能失败,并为为write()指定的任何错误设置errno。

1.4、fileno()

检查和重置流状态。函数原型:

代码语言:javascript
复制
#include <stdio.h>

void clearerr(FILE *stream);

int feof(FILE *stream);

int ferror(FILE *stream);

int fileno(FILE *stream);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//fileno(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

描述: 函数clearer()清除stream指向的流的文件结尾和错误指示符。

函数feof()测试stream指向的流的文件结束指示符,如果设置了该指示符,则返回非零。文件结束指示符只能由函数clearer()清除。

函数ferror()测试stream指向的流的错误指示符,如果设置了该指示符,则返回非零。错误指示器只能通过clearer()函数重置。

函数fileno()检查参数stream并返回其整数描述符。

错误: 这些函数不应失败,也不应设置外部变量errno。(但是,如果fileno()检测到其参数不是有效的流,则必须返回-1并将errno设置为EBADF。)

1.5、fsync()

将处于核心状态的文件与存储设备同步。函数原型:

代码语言:javascript
复制
#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//fsync(): _BSD_SOURCE || _XOPEN_SOURCE || /* since glibc 2.8: */ _POSIX_C_SOURCE >= 200112L
//fdatasync(): _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500

描述:

fsync()将文件描述符fd引用的文件的所有修改的核心数据(即修改的缓冲区缓存页)传输(“刷新”)到磁盘设备(或其他永久存储设备),以便即使在系统崩溃或重新启动后也可以检索到所有更改的信息。这包括写入或刷新磁盘缓存(如果存在)。调用将阻塞,直到设备报告传输已完成。它还刷新与文件关联的元数据信息(请参阅stat() )。

调用fsync()并不一定确保包含该文件的目录中的条目也已到达磁盘。为此,还需要在目录的文件描述符上有fsync()。

fdatasync()类似于fsync(),但不会刷新修改后的元数据,除非需要该元数据才能正确处理后续数据检索。例如,对st_atime或st_mtime的更改(分别是上次访问的时间和上次修改的时间;请参阅stat() )不需要刷新,因为它们对于正确处理后续读取的数据来说是不必要的。另一方面,更改文件大小(st_size,如ftruncate() 所做的那样)将需要刷新元数据。

fdatasync()的目的是减少不需要与磁盘同步所有元数据的应用程序的磁盘活动。

返回值: 成功时,这些系统调用返回零。出错时,返回-1,并适当设置errno。

错误:

标识

含义

EBADF

fd不是有效的打开文件描述符。

EIO

同步期间发生错误。

EROFS、EINVAL

fd被绑定到一个不支持同步的特殊文件。

1.6、setvbuf()

流缓冲操作,函数原型:

代码语言:javascript
复制
#include <stdio.h>

void setbuf(FILE *stream, char *buf);

void setbuffer(FILE *stream, char *buf, size_t size);

void setlinebuf(FILE *stream);

int setvbuf(FILE *stream, char *buf, int mode, size_t size);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//setbuffer(), setlinebuf(): _BSD_SOURCE

描述: 可用的三种缓冲类型是无缓冲、块缓冲和线缓冲。当输出流未缓冲时,信息在写入目标文件或终端时立即显示;当它被块缓冲时,许多字符被保存并写入一个块;当它是行缓冲字符时,将一直保存到输出换行符或从连接到终端设备的任何流(通常是stdin)读取输入为止。功能fflush() 可用于提前推出挡块。

通常,所有文件都是块缓冲的。如果流引用终端(如stdout通常所做的那样),那么它是行缓冲的。默认情况下,标准错误流stderr总是无缓冲的。

setvbuf() 函数可用于任何开放流以更改其缓冲区。mo5de参数必须是以下三个宏之一:

标识

含义

_IONBF

无缓冲

_IOLBF

缓存线

_IOFBF

完全缓冲

除非缓冲文件外,buf参数应指向长度至少为字节大小的缓冲区;将使用此缓冲区代替当前缓冲区。如果参数buf为NULL,则仅影响模式;下一次读或写操作将分配一个新的缓冲区。setvbuf()函数只能在打开流之后和对其执行任何其他操作之前使用。

其他三个调用实际上只是setvbuf() 调用的别名。setbuf() 函数与下列调用完全等效:

代码语言:javascript
复制
setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);

setbuffer() 函数也是和setbuf() 相同的,只是缓冲区的大小取决于调用方,而不是由默认的BUFSIZ决定。

setlinebuf() 函数与以下调用完全等效:

代码语言:javascript
复制
setvbuf(stream, NULL, _IOLBF, 0);

返回值: 函数setvbuf()成功时返回0。失败时返回非零(模式无效或无法满足请求)。它可能会在失败时设置errno。 其他函数不返回值。

二、fwrite与write的关系

fwrite()是C语言中的文件流,应用层的库接口,而write()是系统接口,fwrite()最终会调用write()将数据写入磁盘。

先看看一个简单的示例,分析fwrite()和write()。

代码语言:javascript
复制
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
	}

	fclose(file);

	return 0;
}

用gdb调试的结果为:

代码语言:javascript
复制
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400d2a
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400d2a in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=1, buf=0x6150a0, nbytes=13) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=1, buf=0x6150a0, nbytes=13) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x7ffff7836760 <_IO_2_1_stdout_>, data=0x6150a0, n=13) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=13, data=0x6150a0 "BUFSIZ: 8192\n", fp=0x7ffff7836760 <_IO_2_1_stdout_>) at fileops.c:457
#3  _IO_new_do_write (fp=0x7ffff7836760 <_IO_2_1_stdout_>, data=0x6150a0 "BUFSIZ: 8192\n", to_do=13) at fileops.c:433
#4  0x00007ffff74d598d in _IO_new_file_xsputn (f=0x7ffff7836760 <_IO_2_1_stdout_>, data=<optimized out>, n=1) at fileops.c:1266
#5  0x00007ffff74a597a in _IO_vfprintf_internal (s=0x7ffff7836760 <_IO_2_1_stdout_>, format=0x400fd8 "BUFSIZ: %d\n", ap=ap@entry=0x7fffffffe180) at vfprintf.c:1674
#6  0x00007ffff74aeee6 in __printf (format=<optimized out>) at printf.c:33
#7  0x0000000000400d6b in main ()
(gdb) c
Continuing.
BUFSIZ: 8192

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615720, nbytes=240) at ../sysdeps/unix/sysv/linux/write.c:27
27	in ../sysdeps/unix/sysv/linux/write.c
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615720, nbytes=240) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615720, n=240) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=240, data=0x615720 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=fp@entry=0x614e70, data=0x615720 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., to_do=240) at fileops.c:433
#4  0x00007ffff74d62b0 in _IO_new_file_close_it (fp=fp@entry=0x614e70) at fileops.c:136
#5  0x00007ffff74c8337 in _IO_new_fclose (fp=0x614e70) at iofclose.c:53
#6  0x0000000000400e7f in main ()

可以看到,这是这个fwrite还没有调用write(),write()的调用分别在printf()和fclose()中调用的。 我们把fwrite()的执行次数由10次变为10000次,再来gdb调试看看。

代码语言:javascript
复制
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	//printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10000; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
	}

	fclose(file);

	return 0;
}

gdb结果如下:

代码语言:javascript
复制
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400cea
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400cea in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=1024) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615310, nbytes=1024) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615310, n=1024) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=1024, data=0x615310 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=0x614e70, data=0x615310 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., to_do=1024) at fileops.c:433
#4  0x00007ffff74d598d in _IO_new_file_xsputn (f=0x614e70, data=<optimized out>, n=25) at fileops.c:1266
#5  0x00007ffff74c9927 in __GI__IO_fwrite (buf=0x6152e0, size=1, count=25, fp=0x614e70) at iofwrite.c:39
#6  0x0000000000400de6 in main ()

此时可以看到,write()被fwrite()调用的。

这说明,在c语言的FILE文件库中有缓冲区。

三、fwrite()写入流程

调用fwrite时,如果用户缓冲区满了会调用fflush()或write()将数据写入内核缓冲区;内核调用fsync()才真正把数据写入磁盘中。所以,在调用fsync()之前,数据还没有写入磁盘的。如果要及时或实时将数据写入磁盘中,可以自己调用fsync()函数。

setbuf()可以设置用户缓冲区的大小。 fflush()时触发write(),不是触发fsync(),这个需要注意。 c库缓冲-----fflush---------〉内核缓冲--------fsync-----〉磁盘

可以通过一个例子来证明:

代码语言:javascript
复制
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	//printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
		fflush(file);
	}

	fclose(file);

	return 0;
}

使用gdb进行断点调试的结果是:

代码语言:javascript
复制
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400d2a
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400d2a in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615310, n=24) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=24, data=0x615310 "No.0ROOT ERROR Message!\n", fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=0x614e70, data=0x615310 "No.0ROOT ERROR Message!\n", to_do=24) at fileops.c:433
#4  0x00007ffff74d4728 in _IO_new_file_sync (fp=0x614e70) at fileops.c:813
#5  0x00007ffff74c882d in __GI__IO_fflush (fp=0x614e70) at iofflush.c:40
#6  0x0000000000400e50 in main ()
(gdb) b fsync
Breakpoint 3 at 0x7ffff7560ee0: file ../sysdeps/unix/sysv/linux/fsync.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
27	in ../sysdeps/unix/sysv/linux/write.c
(gdb) disable 2
(gdb) c
Continuing.
[Inferior 1 (process 6926) exited normally]
(gdb) 

从结果上看到,fflush()只触发了write(),给fsync()设置的断点一直没有触发到。要想实时或及时把数据刷到磁盘中,可以自己调用fsync()函数。如下所示:

代码语言:javascript
复制
int fd = fileno(file);//把文件流指针转换为文件描述符
fsync(fd);

四、小结

(1)fwrite() 有缓存,write() 没有缓存。 (2)write() 是系统调用,每次需要将数据写到磁盘,写的大小是要求的大小,依然设计频繁的用户态和内核态切换。 (3)fwrite() 是库函数,每次将数据写入到缓冲区,等缓冲区满了再一次写入磁盘;或者使用fflush冲洗缓冲区。从而减少系统调用,减少内核态和用户态的切换。 (4)fflush()是把C库中的缓冲调用write() 函数写到磁盘(其实是写到内核的缓冲区)。 (5)fsync() 是把内核缓冲刷到磁盘上。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、相关IO接口函数
    • 1.1、fwrite()和fread()
      • 1.2、fclose()
        • 1.3、fflush()
          • 1.4、fileno()
            • 1.5、fsync()
              • 1.6、setvbuf()
              • 二、fwrite与write的关系
              • 三、fwrite()写入流程
              • 四、小结
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档