以前从来不写技术博客,因为我觉得花一两个小时来写篇博客还不如多写几行代码,但我发现我错了,每次需要搭一些框架或者想回忆起某个知识点时,却发现大脑对那些知识点只有一个轮廓,却不清晰,就像近视的人摘掉眼镜后看东西一样不自信。所以,从今天开始,我会将每天所学的知识记录下来,以便以后复习之用。
1、说到Linux文件操作,得先说一下Linux的文件系统。
Linux的文件系统。
文件包含在目录之中,目录由包含子目录,这些构成了Linux文件系统的树状层次结构。可用tree命令查看:
tree -L 1 /
- 普通文件:代码文件、可执行文件等,分为纯文本和二进制文本
- 目录文件:存放其他文件的节点号和名字的文件。
- 链接文件:指向一个文件或目录的文件
- 特殊文件:与外部设备相关,分为块设备和字符设备。
一个文件有目录项、inode和数据块组成,目录项包含文件名和inode节点号,inode包含文件的元信息,如何文件的大小,拥有者,rwx权限,链接数,时间戳,文件数据block等信息,数据块是文件内容存放地。
系统调用和设备驱动程序
只需要调用少量函数即可度文件和设备进行访问和控制,这些函数称为系统调用,由操作系统直接提供,是面向操作系统的接口。
操作系统的核心部分——内核,是一组设备驱动程序,对系统硬件进行控制的底层接口,其封装了所有与硬件相关的特性。ioctl函数提供硬件特有的功能
访问设备驱动程序的底层函数如下:
- open:打开文件或设备
- read:从打开的文件或设备读取数据
- write:向文件或设备写数据
- close:关闭文件
- ioctl:把控制信息传递给设备驱动程序(提供与设备相关的必要控制,用法随设备的不同而不同,所以不具备可移植性)
值得注意的是,只对输入输出操作直接使用系统调用的效率非常低,原因如下:
- 在执行系统调用时,Linux必须从运行用户的代码切换到执行内核代码,然后返回用户代码,这是一个开销。而减少这种开销的好方法是,在程序中尽可能减少系统调用的次数,并且每次系统调用都尽可能的完成多的工作。
- 硬件会限制对底层系统调用一次所能读写的数据块的大小。
为了给设备和磁盘文件提供更高层的借口,Linux提供了一系列的标准库函数,简而言之
2、文件操作
每个运行中的程序被称为进程,它有一些与之关联的文件描述符——打开文件的索引,当一个程序运行时,一般有3个已经打开的文件描述符:
- 0:标准输入
- 1:标注输出
- 2:标准错误
系统调用函数 | 函数原型 | 头文件 |
---|---|---|
write | size_t write(int fildes, const void *buf, size_t nbytes) | unistd.h |
read | size_t read(int fildes, void *buf, size_t nbytes) | unistd.h |
open | int open(const char *path, int oflags, mode_t mode) | fcntl.h |
close | int close(int fildes) | unistd.h |
ioctl | int ioctl(int fildes, int cmd, …) | unistd.h |
lseek | off_t lseek(int fildes, off_t offset, int whence) | unistd.h |
fstat | int fstat(int fildes, struct stat *buf) | unistd.h |
stat | int stat(const char *path, struct stat *buf) | unistd.h |
lstat | int lstat(const char *path, struct stat *buf) | unistd.h |
dup | int dup(int fildes) | unistd.h |
dup2 | int dup2(int fildes, int fildes2) | unistd.h |
1、write函数:fildes表示要操作的文件描述符, buf表示缓冲区, nbytes表示缓冲区的前nbytes个字节。返回值表示写入字节数,但若返回-1,则表示函数调用错误,错误代码保存在全局变量errno里。
2、read函数:fildes表示要操作的文件描述符, buf表示数据区,nbytes表示读入nbytes个字节的数据。返回值表示读入的字节数,如果返回-1,则表示函数调用错误。
3、open函数:path表示要操作的文件路径,如果函数调用成功,则返回一个文件描述符,若失败则返回-1,并设置全局变量errno指明失败原因。这个文件描述符是唯一的,不与其他进程共享的。oflags指定打开文件的操作,具体模式在下表表示, mode表示访问权限。
4、close函数:fildes表示文件描述符。
5、ioctl函数:fildes表示文件描述符, cmd表示操作。
6、lseek函数:fildes表示文件描述,offset指定位置。whence表示偏移的方法,有三个取值,SEEK_SET表示offset是一个绝对位置,SEEK_CUR表示offset是相对于当前位置的一个相对位置,SEEK_END表示offset是相对于文件尾的一个相对位置。lseek函数调用成功会返回一个文件头到当前位置的一个偏移值,若调用失败,会返回-1。
7、fstat函数返回与打开的文件描述符相关的文件状态信息,该信息存储于一个buf之中。
8、stat函数和lstat函数都是通过文件名查询状态信息,但如果文件是一个符号链接时,stat返回的是链接所指向的文件信息,而lstat返回的是符号链接本身的信息。stat结构位于sys/stat.h头文件中。
9、dup和dup2函数复制文件描述符,dup函数返回一个新的文件描述符,dup2通过明确指定目标描述符来把一个文件描述符复制为另外一个。
oflags参数说明
模式 | 说明 |
---|---|
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以读写方式打开 |
O_APPEND | 以追加方式打开 |
O_TRUNC | 文件长度置为0,丢弃已有内容 |
O_CREAT | 按照参数mode创建文件 |
O_EXCL | 与O_CREAT一起使用,确保创建出文件 |
mode参数说明
权限 | 说明 |
---|---|
S_IRUSR | 读权限,文件属主 |
S_IWUSR | 写权限,文件属主 |
S_IXUSR | 执行权限,文件属主 |
S_IRGRP | 读权限,文件所属组 |
S_IWGRP | 写权限,文件所属组 |
S_IXGRP | 执行权限,文件所属组 |
S_IROTH | 读权限,其他用户 |
S_IWOTH | 写权限,其他用户 |
S_IXOTH | 执行权限,其他用户 |
值得注意的是,open的标志实际上是发出设置文件访问权限的请求,所请求的权限是否会被设置是取决于用户掩码umask的值。umask说明如下表。
数字 | 取值 | 含义 |
---|---|---|
1(user) | 0 | 允许属主任何权限 |
4 | 禁止属主读权限 | |
2 | 禁止属主写权限 | |
1 | 禁止属主执行权限 | |
2(group) | 0 | 允许组的任何权限 |
4 | 禁止组的读权限 | |
2 | 禁止组的写权限 | |
1 | 禁止组的执行权限 | |
3(other) | 0 | 允许其他用户任何权限 |
4 | 禁止其他用户的读权限 | |
2 | 禁止其他用户的写权限 | |
1 | 禁止其他用户的执行权限 |
open调用的mode值会与用户掩码的反值做AND操作,例如,如果要禁止组的写和执行权限,同时禁止其他用户的写权限,则umask值应该是1->0, 2->2|1, 3->2,同个数字下的取值OR一起,最终umask值为032。此时如果mode设置为S_IWOTH,创建的文件其他用户也不会有写权限。
3、标准I/O库
标准I/O库及其头文件stdio.h为底层I/O系统调用提供一个通用的接口。同系统调用一样,使用I/O库时,你需要先打开一个文件,其返回值将作为其他函数的参数。与底层文件描述符相应的是流,它被实现为指向结构FILE的指针,程序启动时也有三个流是自动打开的。
- 1、stdin:标准输入
- 2、stdout:标准输出
- 3、stderr:标准错误输出
库函数 | 函数原型 | 头文件 |
---|---|---|
fopen | FILE *fopen(const char *filename, const char *mode) | stdio.h |
fclose | int fclose(FILE *stream) | stdio.h |
fread | size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream) | stdio.h |
fwrite | size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream) | stdio.h |
fflush | int fflush(FILE *stream | stdio.h |
fseek | int fseek(FILE *stream, long int offset, int whence) | stdio.h |
fgetc | int fgetc(FILE *stream) | stdio.h |
getc | int getc(FILE *stream) | stdio.h |
getchar | int getchar() | stdio.h |
fputc | int fputc(int c, FILE *stream) | stdio.h |
putc | int putc(int c, FILE *stream) | stdio.h |
putchar | int putchar(int c) | stdio.h |
fgets | char *fgets(char *s, int n, FILE *stream) | stdio.h |
gets | char *gets(char *s) | stdio.h |
printf | int printf(const char *format, …) | stdio.h |
sprintf | int sprintf(char *s, const char *format, …) | stdio.h |
fprintf | int fprintf(FILE *stream, const char *format) | stdio.h |
scanf | int scanf(const char *format, …) | stdio.h |
fscanf | int fscanf(FILE *stream,const char *format, …) | stdio.h |
sscanf | int sscanf(const char *s, const char *format, …) | stdio.h |
1、fopen函数类似于底层的open系统调用,主要用于文件和终端的输入输出,想对设备机型明确控制的话最好使用底层系统调用。filename参数是文件名,mode指定文件的打开方式,函数调用成功会返回FILE *指针,失败会返回NULL。可用的文件刘和文件描述符一样是有限制的,大小为在stdio.h头文件中定义的FOPEN_MAX的值。
2、fclose函数用于关闭指定的文件流,也能确保数据全部写出。
3、fread函数用于从一个文件里读取数据,数据从文件流stream读到ptr指向的数据缓冲区,size指定每个数据记录的长度,nitems给出要传输的记录个数。调用成功是返回读取到的数据缓冲区的记录个数。
4、fwrite函数与fread函数相似,ptr指向要写入文件流stream的数据缓冲区。
5、fflush函数用于将文件流中还未写出的数据立即写出,fclose函数隐含执行fflush。
6、fseek函数与lseek类似,参数offset和whence和lseek的offset和whence意义完全相似。
7、fgetc和getc函数从文件流读取下一个字符并返回,当到达文件尾或者出现错误时,会返回EOF。getchar函数相当于getc(stdin),表示从标准输入里读取下一个字符。
8、fputc和putc函数把一个字符写到输出文件流中,如果失败,则会返回EOF。putchar函数相当于putc(c, stdout),表示将一个字符写入标准输出流中。
9、fgets函数表示把从文件流中读到的字符写到s指向的字符串里,直到遇到换行符,或已经传输了n-1个字符,或已经到达文件尾。当函数调用成功时会返回一个指向字符串s的指针,如果已经到达文件尾,fgets会设置文件流的EOF标识并返回一个空指针,如果发生错误,则返回一个空指针并设置errno以指出错误类型。gets函数类似fgets,它从标准输入中读取数据并丢弃换行符。
10、printf函数把自己的输出输出到标准输出流,fprintf函数将自己的输出输出到文件流,sprintf函数将自己的输出输出到字符串。
11、scanf函数,fscanf函数和sscanf函数类似于prinf系列的函数,是将读取到的数值存放到变量之中。
mode参数说明
模式 | 说明 |
---|---|
“r”或”rb” | 以只读方式打开文件 |
“w”或”wb” | 以只写方式打开文件,并把文件长度截短为0 |
“a”或”ab” | 以只写方式打开文件,新内容追加到文件尾 |
“r+”或”r+b”或”rb+” | 以更新方式打开,(读或写) |
“w+”或”w+b”或”wb+” | 以更新方式打开, 并把文件长度截短为0 |
“a+”或”a+b”或”ab+” | 以更新方式打开,新内容追加到文件尾 |