Linux系统程序设计--2. 文件I/O
文件I/O
标准C的I/O
FILE结构体
- 下面只列出了5个成员
- 可以观察到,有些函数没有FILE类型的结构体指针例如printf主要是一些标准输出,因为其内部用到了stdin,stdout,stderr
- 查找文件所在的位置:
find \ -name stat.h
- 查找头文件所在的位置。可以使用gcc
#include <sys/stat.h> int main() { return 0; }
gcc -M test.c
代码文件
src
.c
的目标文件
bin
- 编译链接生成的可执行文件
obj
- 存放生成的.o`的目标文件
include
- 存储头文件
缓冲
缓冲类型
- 全缓冲(Fully Buffered):
- 数据被写入缓冲区,当缓冲区满或显式调用
fflush
时才写入目标。 - 通常用于文件I/O,尤其是磁盘文件。
- 数据被写入缓冲区,当缓冲区满或显式调用
- 行缓冲(Line Buffered):
- 数据在遇到换行符时被写入目标。
- 通常用于终端输入输出(如标准输入
stdin
和标准输出stdout
)。
#include<stdio.h>
#include<unistd.h>
int main(void)
{
printf("Hello Iotak\n");
while(1)
{
sleep(1);
}
return 0;
}
sleep函数不是标准C库的一部分,而是POSIX标准的一部分
在大多数Unix-like系统(如Linux和macOS)上,sleep函数定义在unistd.h头文件中。
在Windows上,你可以使用Sleep函数(注意首字母大写),它定义在windows.h头文件中。
- 无缓冲(Unbuffered):
- 每次I/O操作直接与目标交互,没有中间缓冲区。
- 通常用于标准错误
stderr
。
常见的I/O函数及其缓冲特性
- 标准输入/输出函数:
printf
、puts
、putchar
:默认行缓冲(对于终端),全缓冲(对于文件)。scanf
、getchar
、gets
:默认行缓冲(对于终端),全缓冲(对于文件)。
- 文件操作函数:
fprintf
、fputs
、fputc
:默认全缓冲。fscanf
、fgetc
、fgets
:默认全缓冲。fread
、fwrite
:默认全缓冲。fclose
、fflush
:显式刷新缓冲区。setbuf
、setvbuf
:允许手动设置缓冲方式和缓冲区大小。
特殊情况
- 标准错误
stderr
stderr
默认是无缓冲的,这样可以确保错误信息立即显示,不会因为缓冲而延迟。- 使用
setvbuf
可以改变stderr
的缓冲方式。
示例
默认缓冲行为
#include <stdio.h>
int main() {
printf("This is a test.\n"); // 行缓冲,遇到换行符会刷新
fprintf(stderr, "This is an error message.\n"); // 无缓冲,立即显示
return 0;
}
手动设置缓冲方式
#include <stdio.h>
int main() {
char buffer[1024];
setvbuf(stdout, buffer, _IOFBF, sizeof(buffer)); // 设置stdout为全缓冲
printf("This is a test."); // 不会立即显示,因为没有换行符
fflush(stdout); // 显式刷新缓冲区
return 0;
}
总结
- 标准输入/输出:通常行缓冲(对于终端)或全缓冲(对于文件)。
- 标准错误:默认无缓冲。
- 文件操作:默认全缓冲。
- 手动控制:可以使用
setbuf
和setvbuf
函数手动设置缓冲方式和缓冲区大小。
标准输入/输出函数
1. int printf(const char *format, ...);
-
头文件:
<stdio.h>
-
功能:将格式化的数据写入到标准输出(通常是屏幕)。
-
参数:
const char *format
:格式控制字符串。...
:可变参数列表,根据格式字符串中的占位符提供相应的值。
-
返回值:成功时返回打印的字符数;失败时返回一个负值。
-
例子:
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
2. int scanf(const char *format, ...);
-
头文件:
<stdio.h>
-
功能:从标准输入读取格式化输入。
-
参数:
const char *format
:格式控制字符串。...
:可变参数列表,通常是指向变量的指针,用于存储读取的值。
-
返回值:成功时返回成功读取并赋值的数据项数量;到达文件结束或遇到错误时返回EOF。
-
例子:
#include <stdio.h> int main() { int num; printf("Enter an integer: "); scanf("%d", &num); printf("You entered: %d\n", num); return 0; }
3. int puts(const char *s);
-
头文件:
<stdio.h>
-
功能:将字符串写入标准输出,自动添加换行符。
-
参数:
const char *s
:要写入的字符串。
-
返回值:成功时返回非负值;如果发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { puts("Hello, World!"); return 0; }
4. char *gets(char *s);
-
头文件:
<stdio.h>
-
功能:从标准输入读取一行字符串(不推荐使用,存在缓冲区溢出风险)。
-
参数:
char *s
:指向存储读取字符串的缓冲区的指针。
-
返回值:成功时返回指向字符串的指针;如果到达文件末尾或发生错误,则返回NULL。
-
例子:
#include <stdio.h> int main() { char buffer[100]; printf("Enter a line: "); gets(buffer); // 不推荐使用 printf("You entered: %s\n", buffer); return 0; }
5. int getchar(void);
-
头文件:
<stdio.h>
-
功能:从标准输入读取下一个可用字符。
-
参数:无。
-
返回值:成功时返回读取的字符;如果到达文件结束或发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { int ch; printf("Press any key and then press enter: "); ch = getchar(); printf("You pressed: %c\n", ch); return 0; }
6. int putchar(int c);
-
头文件:
<stdio.h>
-
功能:将指定的字符写入到标准输出。
-
参数:
int c
:要写入的字符。
-
返回值:成功时返回写入的字符;如果发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { putchar('A'); return 0; }
文件操作函数
7. FILE *fopen(const char *path, const char *mode);
-
头文件:
<stdio.h>
-
功能:打开或创建一个文件,并返回一个指向该文件的FILE类型指针。
-
参数:
const char *path
:文件路径。const char *mode
:打开模式(如"r"表示只读,"w"表示写入等)。
-
返回值:成功时返回指向文件的指针;如果打开失败,则返回NULL。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fclose(file); return 0; }
8. int fclose(FILE *stream);
-
头文件:
<stdio.h>
-
功能:关闭一个已打开的文件流。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:成功时返回0;如果发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fclose(file); return 0; }
9. int fprintf(FILE *stream, const char *format, ...);
-
头文件:
<stdio.h>
-
功能:将格式化的数据写入指定的流。
-
参数:
FILE *stream
:指向文件流的指针。const char *format
:格式控制字符串。...
:可变参数列表,根据格式字符串中的占位符提供相应的值。
-
返回值:成功时返回打印的字符数;失败时返回一个负值。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("test.txt", "w"); if (file == NULL) { printf("Error opening file.\n"); return 1; } fprintf(file, "This is a test.\n"); fclose(file); return 0; }
10. int fscanf(FILE *stream, const char *format, ...);
-
头文件:
<stdio.h>
-
功能:从指定的流读取格式化输入。
-
参数:
FILE *stream
:指向文件流的指针。const char *format
:格式控制字符串。...
:可变参数列表,通常是指向变量的指针,用于存储读取的值。
-
返回值:成功时返回成功读取并赋值的数据项数量;到达文件结束或遇到错误时返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("test.txt", "r"); if (file == NULL) { printf("Error opening file.\n"); return 1; } char str[100]; fscanf(file, "%s", str); printf("Read from file: %s\n", str); fclose(file); return 0; }
11. char *fgets(char *str, int n, FILE *stream);
-
头文件:
<stdio.h>
-
功能:从流中读取一行(直到换行符或达到指定长度减一)。
-
参数:
char *str
:指向存储读取字符串的缓冲区的指针。int n
:要读取的最大字符数(包括终止的空字符)。FILE *stream
:指向文件流的指针。
-
返回值:成功时返回指向字符串的指针;如果到达文件末尾或发生错误,则返回NULL。
-
例子:
#include <stdio.h> int main() { char buffer[100]; FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fgets(buffer, sizeof(buffer), file); printf("Read from file: %s", buffer); fclose(file); return 0; }
12. int fputs(const char *str, FILE *stream);
-
头文件:
<stdio.h>
-
功能:向指定的流写入一个字符串(不包括终止的空字符)。
-
参数:
const char *str
:要写入的字符串。FILE *stream
:指向文件流的指针。
-
返回值:成功时返回非负值;如果发生错误则返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("test.txt", "w"); if (file == NULL) { printf("Error opening file.\n"); return 1; } const char *message = "This is a message."; fputs(message, file); fclose(file); return 0; }
13. int fgetc(FILE *stream);
-
头文件:
<stdio.h>
-
功能:从指定的流中读取下一个字符。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:成功时返回读取的字符;如果到达文件结束或发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } int ch; while ((ch = fgetc(file)) != EOF) { putchar(ch); } fclose(file); return 0; }
14. int fputc(int c, FILE *stream);
-
头文件:
<stdio.h>
-
功能:将指定的字符写入到指定的流。
-
参数:
int c
:要写入的字符。FILE *stream
:指向文件流的指针。
-
返回值:成功时返回写入的字符;如果发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "w"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fputc('A', file); fclose(file); return 0; }
15. int fseek(FILE *stream, long offset, int whence);
-
头文件:
<stdio.h>
-
功能:设置文件流的位置指针。
-
参数:
FILE *stream
:指向文件流的指针。long offset
:相对于whence
的偏移量。int whence
:位置基准点(如SEEK_SET
表示文件开头,SEEK_CUR
表示当前位置,SEEK_END
表示文件结尾)。
-
返回值:成功时返回0;如果发生错误,则返回非零值。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r+"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fseek(file, 5, SEEK_SET); // 移动到文件开头后的第5个字节 fclose(file); return 0; }
16. long ftell(FILE *stream);
-
头文件:
<stdio.h>
-
功能:获取文件流的当前读写位置。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:成功时返回当前的位置(以字节为单位);如果发生错误,则返回-1L。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fseek(file, 5, SEEK_SET); // 移动到文件开头后的第5个字节 long pos = ftell(file); printf("Current position: %ld\n", pos); fclose(file); return 0; }
17. void rewind(FILE *stream);
-
头文件:
<stdio.h>
-
功能:将文件流的位置指针重置到文件开头。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:无。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fseek(file, 5, SEEK_SET); // 移动到文件开头后的第5个字节 rewind(file); // 重置到文件开头 fclose(file); return 0; }
18. int feof(FILE *stream);
-
头文件:
<stdio.h>
-
功能:检测文件流是否到达文件结束。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:如果文件流到达文件结束,则返回非零值;否则返回0。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } int ch; while ((ch = fgetc(file)) != EOF) { putchar(ch); } if (feof(file)) { printf("End of file reached.\n"); } fclose(file); return 0; }
19. int ferror(FILE *stream);
-
头文件:
<stdio.h>
-
功能:检测文件流是否发生错误。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:如果文件流发生错误,则返回非零值;否则返回0。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("nonexistent.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } int ch; while ((ch = fgetc(file)) != EOF) { if (ferror(file)) { printf("Error reading file.\n"); break; } putchar(ch); } fclose(file); return 0; }
20. void clearerr(FILE *stream);
-
头文件:
<stdio.h>
-
功能:清除文件流的错误标志和文件结束标志。
-
参数:
FILE *stream
:指向文件流的指针。
-
返回值:无。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "r"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } int ch; while ((ch = fgetc(file)) != EOF) { if (ferror(file)) { clearerr(file); // 清除错误标志 printf("Error cleared.\n"); break; } putchar(ch); } fclose(file); return 0; }
21. int remove(const char *filename);
-
头文件:
<stdio.h>
-
功能:删除指定的文件。
-
参数:
const char *filename
:要删除的文件名。
-
返回值:成功时返回0;如果发生错误,则返回非零值。
-
例子:
#include <stdio.h> int main() { if (remove("example.txt") == 0) { printf("File deleted successfully.\n"); } else { printf("Error deleting file.\n"); } return 0; }
22. int rename(const char *oldname, const char *newname);
-
头文件:
<stdio.h>
-
功能:重命名文件。
-
参数:
const char *oldname
:旧文件名。const char *newname
:新文件名。
-
返回值:成功时返回0;如果发生错误,则返回非零值。
-
例子:
#include <stdio.h> int main() { if (rename("oldname.txt", "newname.txt") == 0) { printf("File renamed successfully.\n"); } else { printf("Error renaming file.\n"); } return 0; }
23. void setbuf(FILE *stream, char *buf);
-
头文件:
<stdio.h>
-
功能:设置或取消文件流的缓冲区。
-
参数:
FILE *stream
:指向文件流的指针。char *buf
:缓冲区指针。如果为NULL,则取消缓冲。
-
返回值:无。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "w"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } setbuf(file, NULL); // 取消缓冲 fprintf(file, "This is a test.\n"); fclose(file); return 0; }
24. int setvbuf(FILE *stream, char *buf, int mode, size_t size);
-
头文件:
<stdio.h>
-
功能:设置文件流的缓冲方式和大小。
-
参数:
FILE *stream
:指向文件流的指针。char *buf
:缓冲区指针。如果为NULL,则使用系统分配的缓冲区。int mode
:缓冲方式(如_IOFBF
表示完全缓冲,_IOLBF
表示行缓冲,_IONBF
表示不缓冲)。size_t size
:缓冲区大小。
-
返回值:成功时返回0;如果发生错误,则返回非零值。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "w"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } char buffer[1024]; setvbuf(file, buffer, _IOFBF, sizeof(buffer)); // 设置完全缓冲 fprintf(file, "This is a test.\n"); fclose(file); return 0; }
25. int fflush(FILE *stream);
-
头文件:
<stdio.h>
-
功能:刷新文件流的缓冲区。
-
参数:
FILE *stream
:指向文件流的指针。如果为NULL,则刷新所有输出流。
-
返回值:成功时返回0;如果发生错误,则返回EOF。
-
例子:
#include <stdio.h> int main() { FILE *file = fopen("example.txt", "w"); if (file == NULL) { printf("Failed to open file.\n"); return 1; } fprintf(file, "This is a test.\n"); fflush(file); // 刷新缓冲区 fclose(file); return 0; }
这些函数是标准C语言中最常用的一些I/O函数,涵盖了标准输入/输出和文件操作的基本需求。希望这些信息对你有帮助!如果有任何其他问题或需要进一步的解释,请告诉我。
文件描述符
- 标准IO函数通过
FILE
类型的结构体指针,系统IO函数则是通过文件描述符来完成
文件描述符和文件结构体指针相互的转换
fdopen
:将文件描述符转换为文件流,方便使用标准I/O函数进行操作。fileno
:将文件流转换为文件描述符,方便使用低级I/O系统调用进行操作。
fdopen
函数
- 函数原型
FILE *fdopen(int fd, const char *mode);
-
参数说明
-
int fd
:文件描述符。 -
const char *mode
:打开模式(如"r"表示只读,"w"表示写入,"a"表示追加等)。
-
-
所在头文件
<stdio.h>
-
返回值
-
成功时返回指向文件流的指针。
-
失败时返回
NULL
。
-
-
简单示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
return 1;
}
FILE *fp = fdopen(fd, "r+");
if (fp == NULL) {
perror("fdopen");
close(fd);
return 1;
}
// 使用文件流进行操作
const char *message = "Hello, fdopen!";
fwrite(message, 1, strlen(message), fp);
// 重置文件位置指针
rewind(fp);
char buffer[100];
size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp);
buffer[bytesRead] = '\0';
printf("Read from file: %s\n", buffer);
fclose(fp); // 关闭文件流,同时关闭文件描述符
return 0;
}
fileno
函数
- 函数原型
int fileno(FILE *stream);
-
参数说明
-
FILE *stream
:指向文件流的指针。 -
所在头文件
-
<stdio.h>
-
返回值
-
成功时返回文件描述符。
-
失败时返回-1。
-
简单示例
#include <stdio.h>
#include <unistd.h>
int main() {
FILE *fp = fopen("example.txt", "r+");
if (fp == NULL) {
perror("fopen");
return 1;
}
int fd = fileno(fp);
if (fd == -1) {
perror("fileno");
fclose(fp);
return 1;
}
printf("File descriptor: %d\n", fd);
// 使用文件描述符进行操作
const char *message = "Hello, fileno!";
write(fd, message, strlen(message));
// 重置文件位置指针
lseek(fd, 0, SEEK_SET);
char buffer[100];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1);
buffer[bytesRead] = '\0';
printf("Read from file: %s\n", buffer);
fclose(fp); // 关闭文件流,同时关闭文件描述符
return 0;
}
文件I/O的系统调用
- 文件操作函数的头文件一般都需要加上
unistd.h
标准输入/输出函数
1. ssize_t read(int fd, void *buf, size_t count);
-
头文件:
<unistd.h>
-
功能:从文件描述符
fd
读取最多count
个字节到缓冲区buf
。 -
参数:
int fd
:文件描述符。void *buf
:指向存储读取数据的缓冲区的指针。size_t count
:要读取的最大字节数。
-
返回值:成功时返回实际读取的字节数;到达文件结束时返回0;发生错误时返回-1。
-
例子:
#include <unistd.h> #include <stdio.h> int main() { char buffer[100]; ssize_t bytesRead = read(STDIN_FILENO, buffer, sizeof(buffer) - 1); if (bytesRead == -1) { perror("read"); return 1; } buffer[bytesRead] = '\0'; // 确保字符串以空字符结尾 printf("Read from stdin: %s\n", buffer); return 0; }
2. ssize_t write(int fd, const void *buf, size_t count);
-
头文件:
<unistd.h>
-
功能:将
buf
中的count
个字节写入文件描述符fd
。 -
参数:
int fd
:文件描述符。const void *buf
:指向要写入的数据的缓冲区的指针。size_t count
:要写入的字节数。
-
返回值:成功时返回实际写入的字节数;发生错误时返回-1。
-
例子:
#include <unistd.h> #include <stdio.h> int main() { const char *message = "Hello, World!\n"; ssize_t bytesWritten = write(STDOUT_FILENO, message, strlen(message)); if (bytesWritten == -1) { perror("write"); return 1; } return 0; }
复制案例
- io.h
#ifndef __IO_H__ #define __IO_H__ extern void copy(int fdin,int fdout); #endif
- io.c
#include<io.h> #include<errno.h> #include<fcntl.h> #include<unistd.h> #include<stdio.h> #include<string.h> #include<stdlib.h> #define BUFFER_LEN 1024 void copy(int fdin,int fdout) { char buffer[BUFFER_LEN]; ssize_t size; while((size=read(fdin,buffer,BUFFER_LEN))>0) { if(size<0) { fprintf(stderr,"read error:%s\n",strerror(errno)); exit(1); } else { if(write(fdout,buffer,size)!=size) { fprintf(stderr,"read error:%s\n",strerror(errno)); exit(1); } } }
gcc obj/io -Iinclude -c src/io.c
- -Iinclude
- - -c
- 产生目标文件
gcc bin/cp -Iinclude obj/io.o src/cp.c
或者gcc bin/cp -Iinclude src/io.c src/cp.c
- cp.c
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<fcntl.h> #include<unistd.h> #include<errno.h> #include"io.h" int main(int argc,char ** argv) { if(argc<3) { fprintf(stderr,"usage:%s srcfile destfile \n",argv[0]); exit(1); } int fdin = open(argv[1],O_RDONLY); if(fdin<0) { fprintf(stderr,"%s fdin open fails:%s \n",argv[1],strerror(errno)); exit(1); } else { printf("%s %d\n",argv[1],fdin); } int fdout = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0777); if(fdout<0) { fprintf(stderr,"%s fdout open fails:%s \n",argv[2],strerror(errno)); exit(1); }else{ printf("%s %d\n",argv[2],fdout); } copy(fdin,fdout); close(fdin); close(fdout); return 0; }
文件操作函数
- C库函数的头文件都在
\usr\include
下 - -查找头文件所在的位置。可以使用gcc
#include <sys/stat.h> int main() { return 0; }
gcc -M test.c
3. int open(const char *pathname, int flags, mode_t mode);
-
头文件:
<fcntl.h>
和<sys/stat.h>
-
功能:打开或创建一个文件,并返回一个文件描述符。
-
参数:
const char *pathname
:文件路径。int flags
:打开模式(如O_RDONLY
表示只读,O_WRONLY
表示只写,O_RDWR
表示读写等在头文件<fcntl.h>中定义)。//例如,创建一个用户可读可写的文件: int fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
mode_t mode
:文件权限(仅在创建文件时使用)。-
mode
参数是一个mode_t
类型的值,通常由几个权限位按位或(|
)组合而成。以下是一些常用的权限位及其说明: -
用户权限:
S_IRUSR
:用户读权限(0400)S_IWUSR
:用户写权限(0200)S_IXUSR
:用户执行权限(0100)
-
组权限:
-S_IRGRP
:组读权限(0040)
-S_IWGRP
:组写权限(0020)
-S_IXGRP
:组执行权限(0010) -
其他用户权限
S_IROTH
:其他用户读权限(0004)S_IWOTH
:其他用户写权限(0002)S_IXOTH
:其他用户执行权限(0001)
-
权限组合
你可以通过按位或运算(|
)组合多个权限位来设置文件的权限。例如:-
用户可读可写,组和其他用户不可访问:
mode_t mode = S_IRUSR | S_IWUSR;
-
用户可读可写可执行,组可读可执行,其他用户可读:
mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH;
-
-
以下是一些常见的权限组合示例:
-
用户可读可写:
int fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
-
用户可读可写可执行:
int fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IXUSR);
-
用户可读可写,组可读,其他用户不可访问:
int fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP);
-
用户可读可写可执行,组可读可写,其他用户可读:
int fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IROTH);
-
-
八进制表示法
- 权限也可以用八进制数字表示,每个数字对应一组权限:
- 第一个数字:用户权限
- 第二个数字:组权限
- 第三个数字:其他用户权限例如: - `0600`:用户可读可写,组和其他用户不可访问 - `0755`:用户可读可写可执行,组可读可执行,其他用户可读可执行 - `0644`:用户可读可写,组和其他用户可读 - 使用八进制表示法设置权限:
int fd = open("example.txt", O_CREAT | O_RDWR, 0600);
-
-
返回值:成功时返回文件描述符;如果打开失败,则返回-1。
-
例子:
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> int main() { int fd = open("example.txt", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); if (fd == -1) { perror("open"); return 1; } close(fd); return 0; }
creat
creat
函数是Linux系统中用于创建文件的一个简化版本的open
函数。creat
函数的主要特点是它总是创建一个新的文件,并且只以写模式打开文件。如果文件已经存在,creat
会截断文件并覆盖其内容。
函数原型
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
-
参数说明
-
const char *pathname
:要创建的文件的路径。 -
mode_t mode
:文件的权限模式。
-
-
返回值
-
成功时返回文件描述符(非负整数)。
-
失败时返回-1,并设置
errno
以指示错误类型。
-
-
常用的权限模式
权限模式可以用
mode_t
类型的值表示,通常由几个权限位按位或(|
)组合而成。以下是一些常用的权限位:S_IRUSR
:用户读权限(0400)S_IWUSR
:用户写权限(0200)S_IXUSR
:用户执行权限(0100)S_IRGRP
:组读权限(0040)S_IWGRP
:组写权限(0020)S_IXGRP
:组执行权限(0010)S_IROTH
:其他用户读权限(0004)S_IWOTH
:其他用户写权限(0002)S_IXOTH
:其他用户执行权限(0001)
-
示例
-
#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main() { int fd; const char *filename = "example.txt"; const char *message = "Hello, creat!"; char buffer[100]; // 创建文件,权限为用户可读可写 fd = creat(filename, S_IRUSR | S_IWUSR); if (fd == -1) { perror("creat"); exit(EXIT_FAILURE); } // 写入数据 ssize_t bytesWritten = write(fd, message, strlen(message)); if (bytesWritten == -1) { perror("write"); close(fd); exit(EXIT_FAILURE); } // 重置文件位置指针 lseek(fd, 0, SEEK_SET); // 读取数据 ssize_t bytesRead = read(fd, buffer, sizeof(buffer) - 1); if (bytesRead == -1) { perror("read"); close(fd); exit(EXIT_FAILURE); } buffer[bytesRead] = '\0'; // 确保字符串以空字符结尾 printf("Read from file: %s\n", buffer); // 关闭文件 close(fd); return 0; }
-
4. int close(int fd);
-
头文件:
<unistd.h>
-
功能:关闭一个已打开的文件描述符。
-
参数:
int fd
:文件描述符。
-
返回值:成功时返回0;如果发生错误,则返回-1。
5. off_t lseek(int fd, off_t offset, int whence);
- 可以计算文件的大小
printf("%s file length:%ld\n",argv[1],lseek(fdin,0L,SEEK_END));
-
头文件:
<unistd.h>
-
功能:设置文件描述符
fd
的读写位置。 -
参数:
int fd
:文件描述符。off_t offset
:相对于whence
的偏移量。如 long 或 long longint whence
:位置基准点(如SEEK_SET
表示文件开头,SEEK_CUR
表示当前位置,SEEK_END
表示文件结尾)。
-
返回值:成功时返回新的文件位置;如果发生错误,则返回-1。
-
例子:
#include <fcntl.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> int main() { int fd = open("example.txt", O_RDWR); if (fd == -1) { perror("open"); return 1; } off_t new_pos = lseek(fd, 5, SEEK_SET); // 移动到文件开头后的第5个字节 if (new_pos == -1) { perror("lseek"); close(fd); return 1; } close(fd); return 0; }
生成空洞文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
char *buffer = "0123456789";
int main(int argc,char *argv[])
{
if(argc<2)
{
fprintf(stderr,"parameter is less\n");
exit(0);
}
int fd = open(argv[1],O_CREAT|O_RDWR,0777);
if(fd==-1) // 文件打开s失败
{
fprintf(stderr,"%s file open fail\n",argv[1]);
exit(1);
}
lseek(fd,20L,SEEK_SET);
if(write(fd,buffer,strlen(buffer)*sizeof(char))==-1)
{
fprintf(stderr,"write error %s\n",argv[1]);
exit(1);
}
return 0;
}
- 查看文件的ASCALL码
od -c hole
- 查看文件磁盘块大小
sudo tune2fs -l /dev/sda1
- 我们每次一次io读写文件,读写的基本单位是一个磁盘块,一个磁盘块的大小是4096个字节。所以缓存大小设置为磁盘块的大小
6. int dup(int oldfd);
- **是将oldfd的文件描述符表的指针复制给newfd,使oldfd和newfd都指向oldfd的文件描述符表的指针**
-
头文件:
<unistd.h>
-
功能:复制一个文件描述符。
-
参数:
int oldfd
:要复制的文件描述符。
-
返回值:成功时返回新的文件描述符;如果发生错误,则返回-1。
-
例子:
#include <unistd.h> #include <stdio.h> int main() { int fd = open("example.txt", O_RDWR); if (fd == -1) { perror("open"); return 1; } int new_fd = dup(fd); if (new_fd == -1) { perror("dup"); close(fd); return 1; } close(fd); close(new_fd); return 0; }
实现cat命令
#include"io.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc, char *argv[])
{
int fd_in = STDIN_FILENO;
int fd_out = STDOUT_FILENO;
for(int i=1;i<argc;i++) // 这里是为了实现输入 cat file.txt 的情况
{
fd_in = open(argv[i],O_RDONLY);
if(fd_in<0)
{
perror("open error");
continue;
}
copy(fd_in,fd_out);
close(fd_in);
}
if(argc==1) // 这里实现的是只是输入 cat命令时的情况
copy(fd_in,fd_out);
return 0;
}
实现±重定向
mcat + file.txt // 将file.txt输出到屏幕中,这要求将file.txt以只读方式打开之后,将其文件描述符表的指针复制给标准输入
mcat - file.txt //将屏幕输入的内容输出到file.txt中,这要求以可读方式打开file.txt之后,将其文件描述符表指针复制给标准输出
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include"io.h"
/*
bin/mcat + file +为输入从定向
bin/mcat - file - 为输出重定向
*/
int main(int argc,char *argv[])
{
int fd_in,fd_out;
int flag = 0;
for(int i =1;i<argc;i++)
{
if(!strcmp("+",argv[i]))
{
fd_in = open(argv[++i],O_RDONLY);
if(fd_in<0)
{
perror("poen error");
exit(1);
}
// 将标准输入重定向到文件
if(dup2(fd_in,STDIN_FILENO)!=STDIN_FILENO)
{
perror("dup2 error");
exit(1);
}
close(fd_in);
}
else if(!strcmp("-",argv[i]))
{
// 将文件的内容不是输出到屏幕而是输出到一个文件中
fd_out = open(argv[++i],O_WRONLY|O_CREAT|O_TRUNC,0777);
if(fd_out<0)
{
perror("open error");
exit(1);
}
if(dup2(fd_out,STDOUT_FILENO)!=STDOUT_FILENO)
{
perror("dup2 error");
exit(1);
}
close(fd_out);
}
else
{
flag = 1;
fd_in = open(argv[i],O_RDONLY);
if(fd_in<0)
{
perror("open error");
exit(1);
}
if(dup2(fd_in,STDIN_FILENO))
{
perror("dup2 error");
exit(1);
}
copy(STDIN_FILENO,STDOUT_FILENO);
close(fd_in);
}
}
if(!flag)
copy(STDIN_FILENO,STDOUT_FILENO);
return 0;
}
文件描述符的复制
7. int dup2(int oldfd, int newfd);
-
头文件:
<unistd.h>
-
功能:复制一个文件描述符,并将其绑定到另一个文件描述符。
-
参数:
int oldfd
:要复制的文件描述符。int newfd
:新的文件描述符。
-
返回值:成功时返回新的文件描述符;如果发生错误,则返回-1。
-
例子:
#include <unistd.h> #include <stdio.h> int main() { int fd = open("example.txt", O_RDWR); if (fd == -1) { perror("open"); return 1; } int new_fd = dup2(fd, 3); // 将fd复制到文件描述符3 if (new_fd == -1) { perror("dup2"); close(fd); return 1; } close(fd); close(new_fd); return 0; }
文件操作的内核数据结构
int fcntl(int fd, int cmd, … /* arg */ );
fcntl
函数是 Unix 和类 Unix 操作系统中用于文件描述符控制的多功能函数。它可以执行多种与文件描述符相关的操作,如复制文件描述符、获取和设置文件状态标志、获取和设置文件锁等。
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
-
fd
:文件描述符,表示要操作的文件或 I/O 对象。 -
cmd
:命令,指定要执行的操作。常见的命令包括:F_DUPFD
:复制文件描述符。F_GETFD
:获取文件描述符的标志。F_SETFD
:设置文件描述符的标志。F_GETFL
:获取文件状态标志。F_SETFL
:设置文件状态标志。F_GETLK
:获取文件锁。F_SETLK
:设置文件锁。F_SETLKW
:设置文件锁,等待锁释放。
-
arg
:根据cmd
的不同,第三个参数可能是整数、指向struct flock
的指针或其他类型的参数。 -
- 复制文件描述符
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int old_fd = 0; // 标准输入文件描述符
int new_fd;
// 复制文件描述符
new_fd = fcntl(old_fd, F_DUPFD, 100); // 从100开始寻找最小的未使用的文件描述符
if (new_fd == -1) {
perror("fcntl F_DUPFD");
return 1;
}
printf("Old FD: %d, New FD: %d\n", old_fd, new_fd);
// 关闭新旧文件描述符
close(old_fd);
close(new_fd);
return 0;
}
-
- 获取和设置文件描述符标志
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = Ⅰ; // 标准输入文件描述符
int flags;
// 获取文件描述符标志
flags = fcntl(fd, F_GETFD);
if (flags == -1) {
perror("fcntl F_GETFD");
return 1;
}
printf("Current flags: %d\n", flags);
// 设置文件描述符标志
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
perror("fcntl F_SETFD");
return 1;
}
// 再次获取文件描述符标志
flags = fcntl(fd, F_GETFD);
if (flags == -1) {
perror("fcntl F_GETFD");
return 1;
}
printf("New flags: %d\n", flags);
return 0;
}
-
- 获取和设置文件状态标志
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = Ⅰ; // 标准输入文件描述符
int flags;
// 获取文件状态标志
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
return 1;
}
printf("Current file status flags: %d\n", flags);
// 设置文件状态标志
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
return 1;
}
// 再次获取文件状态标志
flags = fcntl(fd, F_GETFL);
if (flags == -1) {
perror("fcntl F_GETFL");
return 1;
}
printf("New file status flags: %d\n", flags);
return 0;
}
-
- 文件锁
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/file.h>
int main() {
int fd;
struct flock lock;
// 打开文件
fd = open("example.txt", O_RDWR | O_CREAT, 06½);
if (fd == -1) {
perror("open");
return 1;
}
// 初始化文件锁结构
lock.l_type = F_WRLCK; // 写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // 整个文件
lock.l_pid = getpid();
// 设置文件锁
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl F_SETLK");
close(fd);
return 1;
}
// 释放文件锁
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) == -1) {
perror("fcntl F_SETLK");
close(fd);
return 1;
}
// 关闭文件
close(fd);
return 0;
}
总结
fcntl
函数是一个非常强大的工具,可以用于多种文件描述符相关的操作。以下是一些常见的操作:
- 复制文件描述符:
F_DUPFD
- 获取和设置文件描述符标志:
F_GETFD
和F_SETFD
- 获取和设置文件状态标志:
F_GETFL
和F_SETFL
- 文件锁:
F_GETLK
、F_SETLK
和F_SETLKW
设置文件标准标志
void set_fl(int fd,int flag)
{
int val = fcntl(fd,F_GETFL);// 获取原来的文件状态n标志
val|=flag; // 增加新的文件状态标志
if(fcntl(fd,F_SETFL,val)<0)
fprintf(stderr,"fcntl error %s\n",strerror(errno));
}
void clr_fl(int fd,int flag)
{
int val = fcntl(fd,F_GETFL);
// 清除指定的文件状态标志
val&=~flag;
if(fcntl(fd,F_SETFL,val)<0)
fprintf(stderr,"fcntl error %s",strerror(errno));
}
文件描述符标志和文件状态标志的区别
在 fcntl
函数中,文件描述符标志(File Descriptor Flags)和文件状态标志(File Status Flags)是两种不同类型的状态标志,它们各自有不同的用途和意义。
文件描述符标志(File Descriptor Flags)
文件描述符标志是与特定文件描述符相关联的标志,而不是与文件本身相关联的标志。这些标志通常影响文件描述符的行为,特别是在进程执行 exec
系列函数时。
常见的文件描述符标志
FD_CLOEXEC
:这是一个非常重要的标志。当设置此标志时,文件描述符将在执行exec
系列函数时自动关闭。如果没有设置此标志,文件描述符将保持打开状态。
设置和获取文件描述符标志
-
获取文件描述符标志:
int flags = fcntl(fd, F_GETFD); if (flags == -1) { perror("fcntl F_GETFD"); return 1; }
-
设置文件描述符标志:
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { perror("fcntl F_SETFD"); return 1; }
文件状态标志(File Status Flags)
文件状态标志是与文件本身相关联的标志,它们影响文件的打开方式和 I/O 操作的行为。这些标志通常在打开文件时通过 open
函数的 flags
参数设置,也可以在文件打开后通过 fcntl
函数修改。
常见的文件状态标志
O_RDONLY
:只读模式。O_WRONLY
:只写模式。O_RDWR
:读写模式。O_APPEND
:每次写操作都追加到文件末尾。O_NONBLOCK
:非阻塞模式。O_SYNC
:同步写操作,确保数据立即写入磁盘。O_CREAT
:如果文件不存在,则创建文件。O_TRUNC
:如果文件已存在且为只写或读写模式,则截断文件长度为0。O_EXCL
:与O_CREAT
一起使用,如果文件已存在,则打开失败。
设置和获取文件状态标志
-
获取文件状态标志:
int flags = fcntl(fd, F_GETFL); if (flags == -1) { perror("fcntl F_GETFL"); return 1; }
-
设置文件状态标志:
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL"); return 1; }
主要区别
-
作用对象:
- 文件描述符标志:作用于特定的文件描述符,影响文件描述符的行为。
- 文件状态标志:作用于文件本身,影响文件的打开方式和 I/O 操作的行为。
-
使用场景:
- 文件描述符标志:主要用于控制文件描述符在
exec
系列函数执行时的行为。 - 文件状态标志:主要用于控制文件的打开方式和 I/O 操作的行为。
- 文件描述符标志:主要用于控制文件描述符在
-
标志位:
- 文件描述符标志:主要标志是
FD_CLOEXEC
。 - 文件状态标志:包括
O_RDONLY
、O_WRONLY
、O_RDWR
、O_APPEND
、O_NONBLOCK
、O_SYNC
等。
- 文件描述符标志:主要标志是
示例
以下是一个示例,展示了如何分别获取和设置文件描述符标志和文件状态标志:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd;
int fd_flags, fl_flags;
// 打开文件
fd = open("example.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("open");
return 1;
}
// 获取文件描述符标志
fd_flags = fcntl(fd, F_GETFD);
if (fd_flags == -1) {
perror("fcntl F_GETFD");
close(fd);
return 1;
}
printf("Current FD flags: %d\n", fd_flags);
// 设置文件描述符标志
if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) {
perror("fcntl F_SETFD");
close(fd);
return 1;
}
printf("New FD flags: %d\n", fcntl(fd, F_GETFD));
// 获取文件状态标志
fl_flags = fcntl(fd, F_GETFL);
if (fl_flags == -1) {
perror("fcntl F_GETFL");
close(fd);
return 1;
}
printf("Current file status flags: %d\n", fl_flags);
// 设置文件状态标志
if (fcntl(fd, F_SETFL, fl_flags | O_NONBLOCK) == -1) {
perror("fcntl F_SETFL");
close(fd);
return 1;
}
printf("New file status flags: %d\n", fcntl(fd, F_GETFL));
// 关闭文件
close(fd);
return 0;
}
总结
- 文件描述符标志:与特定文件描述符相关联,影响文件描述符的行为,特别是
exec
时的行为。 - 文件状态标志:与文件本身相关联,影响文件的打开方式和 I/O 操作的行为。
希望这些信息对你有帮助!如果你有任何其他问题或需要进一步的解释,请告诉我。
文件的原子操作
I/O处理模型
非阻塞IO
-
设置阻塞方式,睡眠之后会等待输入
#include<stdio.h> #include<fcntl.h> #include<unistd.h> #include<string.h> #include<stdlib.h> int main() { char buffer[4096] = {'\0'}; ssize_t size = 0; sleep(5); size = read(STDIN_FILENO,buffer,sizeof(buffer)); // 默认是阻塞的 if(size<0) { perror("read error"); exit(1); } else if(size==0) { printf("read finished!\n"); } else { if(write(STDOUT_FILENO,buffer,size)!=size) perror("write error"); } return 0; }
-
当设置为阻塞方式时,没有输入内容会报错
int main() { char buffer[4096] = {'\0'}; ssize_t size = 0; set_fl(STDIN_FILENO,O_NONBLOCK); sleep(5); size = read(STDIN_FILENO,buffer,sizeof(buffer)); // 设置非阻塞 if(size<0) { perror("read error"); exit(1); } else if(size==0) { printf("read finished!\n"); } else { if(write(STDOUT_FILENO,buffer,size)!=size) perror("write error"); } return 0; }