系统和进程信息与文件IO缓冲
本文是作者阅读TLPI(The Linux Programer Interface的总结),为了突出重点,避免一刀砍,我不会过多的去介绍基本的概念和用法,我重点会去介绍原理和细节。因此 对于本文的读者,至少要求读过APUE,或者是实际有写过相关代码的程序员,因为知识有点零散,所以我会尽可能以FAQ的形式呈现给读者。
procfs其实是一种内核将内核中的数据结构暴露到用户空间的一种方式,通过查看proc目录下的文件就可以获取的内核的一些数据结构,可是我们如何在proc目录下,创建自己的文件来将内核中的一些数据导出到用户空间呢?,这就需要涉及到内核模块编程和proc文件系统相关的内核API了,我推荐给大家我之前写的两篇关于proc内核态编程相关的文章
procfs — The proc filesystem is a pseudo-filesystem which provides an interface to kernel data structures.
sysfs — The filesystem for exporting kernel objects.
procfs 历史最早,最初就是用来跟内核交互的唯一方式,用来获取处理器、内存、设备驱动、进程等各种信息。sysfs是 Linux 内核中设计较新的一种虚拟的基于内存的文件系统,它的作用与 proc 有些类似,但除了与 proc相同的具有查看和设定内核参数功能之外,还有为 Linux 统一设备模型作为管理之用。相比于 proc 文件系统,使用 sysfs导出内核数据的方式更为统一,并且组织的方式更好,它的设计从 proc 中吸取了很多教训。sysfs 跟 kobject 框架紧密联系,而 kobject 是为设备驱动模型而存在的,所以 sysfs 是为设备驱动服务的。
procfs中查看线程并没有像进程那样只管,要想查看线程,需要知道这个线程对应的进程PID号,通过PID号,进入/proc/PID/task/TID/
其中TID是线程ID,这个目录包含了该线程的一些信息。
出于速度和效率的考虑。系统I/O调用(即内核)和标准C语言库I/O函数,在操作磁盘文件时会对数据进行缓冲。
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
mode:
_IONBF 不缓冲
_IOLBF 行缓冲
_IOFBF 全缓冲
通过setvbuf结合不同的mode,可以设置缓冲的类型,和大小,我们也可以通过fflush
来刷新缓冲区。
#include <stdio.h>
int fflush(FILE *stream);
SUSv3将同步I/O完成定义为: 某一I/O操作,要么已成功完成到磁盘的数据传递,要么被诊断为不成功。SUSv3定义了两种不同类型的同步I/O完成。
内核缓冲的缓冲区是在内核态的,没办法通过像stdio库的setvbuf那样给它指定一个用户态的缓冲区,只能控制缓冲区的刷新策略等。
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void)
fsync
用于将描述符fd相关的所有元数据都刷新到磁盘上,相当于强制使文件处于同步I/O文件完整性。
fdatasync
作用类似于fsync,只是强制文件处于数据完整性,确保数据已经传递到磁盘上。
sync
仅在所有数据(数据块,元数据)已传递到磁盘上时才返回。
这些函数都只能刷新一次缓冲,此后每次发生的I/O操作都需要再次调用上面的三个系统调用来刷新缓冲,为此可以通过设置描述符的属性来保证每次IO的刷新缓冲。打开文件的时候设置O_SYNC
,相当于每次发生IO操作后都调用fsync
和fdatasync
系统调用,使用O_DSYNC
标志则要求写操作是同步I/O数据完整性的也就是相当于调用fdatasync
,O_RSYNC
标志需要结合O_DSYNC
和O_SYNC
标志一起使用,将这些标志的写操作作用结合到读操作中,也就说在读操作之前,会先按照O_DSYNC
或O_SYNC
对写操作的要求,完成所有待处理的写操作后才开始读。
注: glibc库中将O_FSYNC
和O_SYNC
定义为同义。
注: Linux提供了非标准的sync_file_range,当刷新文件数据时该调用提供比fdatasync调用更为精确的刷新控制,调用者能够指定待刷新的文件区域,并且还能制定标志,以控制该系统调用在遭遇写磁盘时是否阻塞。具体的使用方式详见man文档。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h>
int sync_file_range(int fd, off64_t offset, off64_t nbytes,
unsigned int flags);
注:linux提供了posix_fadvise
系统调用允许进程就自身访问文件数据时可能采取的模式通知内核,内核可以根据posix_fadvise
所提供的信息来优化缓冲区告诉缓存的使用,进而提高进程和整个系统的性能。
Linux允许应用程序在执行磁盘I/O时绕过缓冲区高速缓存,从用户空间直接将数据传递到文件或磁盘设备上,这我们称之为直接I/O,或者裸I/O。直接I/O只适用于特定I/O需求的应用,例如:数据库系统,其高速缓存和I/O优化机制自成一体,无需内核消耗CPU时间和内存去完成相同任务。通过在打开文件的时候指定O_DIRECT
标志设置文件读写为直接IO的方式。使用直接IO存在一个问题,就是I/O得对齐限制,因为直接I/O涉及对磁盘的直接访问,所以在执行I/O时,必须遵守一些限制。
不遵守上述任一限制将导致EINVAL错误,下面是一个使用直接IO的例子:
#define _GNU_SOURCE
#include <fcntl.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
ssize_t length,alignment;
off_t offset;
void *buf;
if(argc < 2 || strcmp(argv[1],"--help") == 0) {
fprintf(stderr,"%s file \n",argv[0]);
exit(EXIT_FAILURE);
}
//length和offset必须是块大小的整数倍,只要其中一个不是,那么read将返回EINVAL错误,
//这里的512和0都是块大小的整数倍(块大小一般为512字节),如果offset或length改成256,那么将导致返回EINVAL错误。
length = 512;
offset = 0;
alignment = 4096;
fd = open(argv[1],O_RDONLY|O_DIRECT);
if(fd == -1) {
perror("open fd");
exit(EXIT_FAILURE);
}
//分配一个地址是alignment*2的整数倍,大小是length+alignment的内存块,
//其中alignment *2 需要是的2的幂
buf = (char*)memalign(alignment * 2,length + alignment) + alignment;
if(buf == NULL) {
perror("memalign");
exit(EXIT_FAILURE);
}
if(lseek(fd,offset,SEEK_SET) == -1) {
perror("lseek");
exit(EXIT_FAILURE);
}
int numRead = read(fd,buf,length);
if(numRead == -1) {
perror("read");
exit(EXIT_FAILURE);
}
printf("read %ld bytes\n",(long)numRead);
exit(EXIT_SUCCESS);
}