目录
- 1、open 函数
- 2、create 函数
- 3、write 函数
- 4、read 函数
- 5、lseek 函数
- 6、access 函数
- 7、unlink 函数
- 8、remove 函数
- 9、fcntl 函数
- 写锁互斥锁示例
- 读锁共享锁示例
1、open 函数
头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
函数定义
int open(const char *pathname, int flags); //一般用在已经才能在的文件
int open(const char *pathname, int flags, mode_t mode); //用在新创建,并且自己显式设置权限
参数说明
pathname
要打开的文件路径(可以是绝对路径或相对路径)。
flags
文件打开模式,用于指定打开文件的方式。以下是常用的标志:
必选标志(必须指定其中一个):
O_RDONLY
:只读模式。O_WRONLY
:只写模式。O_RDWR
:读写模式。
可选标志(可以组合使用):
O_CREAT
:如果文件不存在,则创建文件。
O_EXCL
:与O_CREAT
一起使用,如果文件已存在,则返回错误。
O_TRUNC
:如果文件存在且为普通文件,则将其长度截断为 0。
O_APPEND
:以追加模式打开文件,每次写操作都会将数据追加到文件末尾。
O_NONBLOCK
:以非阻塞模式打开文件。
O_SYNC
:每次写操作都会等待数据写入磁盘。
mode
当使用 O_CREAT
标志时,需要指定文件的权限模式(八进制表示)。例如:
0644
:文件所有者可读写,其他用户只读。
0755
:文件所有者可读、写、执行,其他用户可读、执行。
0664 的每一位含义如下:
第一位 0:忽略(通常用于特殊权限,如 SUID、SGID 等)。
第二位 6:文件所有者具有读 ® 和写 (w) 权限。
第三位 6:文件所属组具有读 ® 和写 (w) 权限。
第四位 4:其他用户具有读 ® 权限
通过umask
命令查看系统权限掩码
mode的实际权限是减去权限掩码后的权限,比如创建时mode=0666,权限掩码是0002,那么实际的权限是0664。如果mode=0775,实际还是0775,因为umask的0002是去除w权限,而mode中的5是r-x不包含w权限,所以就不减去2,还是0775。
返回值
成功时,返回一个文件描述符(非负整数)。一般成功是3(因为0,1,2被stdin
、stdout
、stderr
占用了)
失败时,返回 -1,并设置 errno 以指示错误类型。
注意:
open()可以打开设备文件,但是不能创建设备文件
示例:
创建并打开一个新文件(读写模式)
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
const char *path = "hello.txt";
int fd = open(path,O_WRONLY | O_CREAT | O_TRUNC,0664);
if (fd < 0)
{
perror("open file error");
return -1;
}
printf("fd = %d\n",fd);
close(fd);
}
2、create 函数
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数定义
int creat(const char *pathname, mode_t mode);
参数说明、返回值
同上
文件权限
文件的实际权限由 mode 参数和当前进程的 umask 共同决定。
计算公式:实际权限 = mode & ~umask。
例如: 如果 mode 为 0666,umask 为 0022,则实际文件权限为 0644
注意事项
- creat() 函数只能以只写模式(O_WRONLY)打开文件。如果需要以读写模式打开文件,应使用 open() 函数。
- 如果文件已存在,creat() 会清空文件内容。如果需要保留文件内容,应使用 open() 函数,并避免使用 O_TRUNC 标志。
- creat() 函数在现代编程中较少使用,通常直接使用 open() 函数替代。
与 open() 的关系
creat() 函数是 open() 函数的一个特例,以下两种调用是等价的:
int fd1 = creat("file.txt", 0644);
int fd2 = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = creat("hello2.txt", 0664);
if (fd < 0)
{
perror("oppen file error");
return -1;
}
close(fd);
}
3、write 函数
头文件
#include <unistd.h>
函数定义
ssize_t write(int fd, const void *buf, size_t count);
参数说明
fd:文件描述符,表示要写入的目标(例如文件、管道、套接字等)。
buf:指向要写入数据的缓冲区的指针。
count:要写入的字节数。
返回值
- 成功时,返回实际写入的字节数(ssize_t 类型)。
- 失败时,返回 -1,并设置 errno 以指示错误类型。
示例
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
typedef struct student
{
char name[16];
int age;
}stu_t;
int main()
{
const char *path = "hello2.txt";
int fd = open(path,O_WRONLY | O_CREAT | O_TRUNC,0664);
if (fd < 0)
{
perror("oppen file error");
return -1;
}
printf("fd = %d\n",fd);
#if 1
//操作
char *str = "hello world\n";
char buf[128] = "hello world";
int len1 = write(fd,str,strlen(str));
if(len1 < 0)
{
perror("write error");
close(fd);
return -1;
}
int len2 = write(fd,str,sizeof(str));
if(len2 < 0)
{
perror("write error");
close(fd);
return -1;
}
printf("len1 = %d\n",len1);
printf("len2 = %d\n",len2);
#endif
#if 1
stu_t stu = {"zhangsan",18};
int len = write(fd,&stu,sizeof(stu_t));
if(len < 0)
{
perror("write data error");
close(fd);
return -1;
}
printf("len = %d\n",len); //20
#endif
close(fd);
}
4、read 函数
头文件
#include <unistd.h>
函数定义
ssize_t read(int fd, void *buf, size_t count);
参数说明
- fd:文件描述符,表示要读取的目标(例如文件、管道、套接字等)。
- buf:指向存储读取数据的缓冲区的指针。
- count:要读取的最大字节数。
返回值
- 成功时,返回实际读取的字节数(ssize_t 类型)。
- 返回 0 表示已到达文件末尾(EOF)。 //可以判断是否为空文件
- 失败时,返回 -1,并设置 errno 以指示错误类型。
示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
typedef struct student
{
char name[16];
int age;
} stu_t;
int main()
{
const char *path = "hello2.txt";
int fd = open(path, O_RDONLY);
if (fd < 0)
{
perror("oppen file error");
return -1;
}
printf("fd = %d\n", fd);
#if 0 //循环读取
char buf[5] = "";
while (1)
{
memset(buf,0,sizeof(buf)); //主要用于判断最后一次读的内容
int len = read(fd, buf, sizeof(buf) - 1); // 尾0留一个字节
if (len < 0)
{
perror("read file error");
close(fd);
return -1;
}
if (len == 0) // len == 0 ,表示读到文件末尾
break;
printf("len = %d\n", len);
printf("buf = %s\n", buf);
}
#endif
#if 1
stu_t stu = {0}; //读取结构体内容
int len = read(fd,&stu,sizeof(stu_t));
if(len < 0)
{
perror("read file error");
close(fd);
return -1;
}
printf("len = %d stu.name = %s stu.age = %d\n",len,stu.name,stu.age);
#endif
close(fd);
}
5、lseek 函数
头文件
#include <sys/types.h>
#include <unistd.h>
函数定义
off_t lseek(int fd, off_t offset, int whence);
参数说明
- fd:文件描述符,表示要操作的文件。
- offset:偏移量,表示要移动的字节数。 正数向右,负数向左
- whence:基准位置,决定如何解释 offset 的值。它可以是以下常量之一:
- SEEK_SET:从文件开头开始计算偏移量。
- SEEK_CUR:从当前文件指针位置开始计算偏移量。
- SEEK_END:从文件末尾开始计算偏移量。
返回值
- 成功时,返回新的文件指针位置(从文件开头计算的字节数)。
- 失败时,返回 -1,并设置 errno 表示错误原因。
注意事项
- 对于普通文件,lseek() 允许将文件偏移量设置为超出文件末尾的位置。在这种情况下,后续的 write() 操作会扩展文件大小。
- 对于管道、FIFO 或套接字,lseek() 不可用,因为这些设备不支持随机访问。
- lseek() 不会导致 I/O 操作,它只是修改文件偏移量。
特殊用法
- 获取文件大小:将文件偏移量移动到文件末尾,并返回当前偏移量。
off_t file_size = lseek(fd, 0, SEEK_END);
- 扩展文件大小:将文件偏移量移动到超出文件末尾的位置,并写入数据。也叫空洞文件
- 空洞文件的特性是:
文件中存在未写入数据的区域(空洞)。
这些空洞不会占用实际的磁盘空间。
文件系统会记录文件的逻辑大小,但实际占用的磁盘空间只包括已写入数据的部分。
- 空洞文件的特性是:
lseek(fd, 100, SEEK_END);
write(fd, "", 1); // 扩展文件大小
示例
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
const char *path = "hello2.txt";
。
int fd = open(path,O_RDWR,0664);
if (fd < 0)
{
perror("oppen file error");
return -1;
}
printf("fd = %d\n",fd);
#if 1
// lseek(fd,1,SEEK_SET);
lseek(fd,-2,SEEK_END);
char buf[128] = "";
read(fd,buf,sizeof(buf) -1);
printf("buf = %s\n",buf);
//获取文件大小
int size = lseek(fd,0,SEEK_END); //先移动到末尾,再获取当前的位置
printf("%d\n",size);
//创建空洞文件
int size2 = lseek(fd,50,SEEK_SET);
write(fd,"a",1);
printf("%d\n",size2);
#endif
#if 0
char *str = "aaaaaaaa";
write(fd,str,strlen(str));
lseek(fd,0,SEEK_SET);
char buf[128] = "";
read(fd,buf,sizeof(buf)-1);
printf("buf = %s\n",buf);
close(fd);
#endif
}
6、access 函数
头文件
#include <unistd.h>
函数定义
int access(const char *pathname, int mode);
参数说明
- pathname:文件或目录的路径。
- mode:检查的权限模式,可以是以下值的组合:
- F_OK:检查文件是否存在。
- R_OK:检查是否具有读权限。,而不是实际用户 ID 和实际组 ID。
- X_OK: 检查是否有可执行权限
- W_Ok: 检查是否有可写权限
- 如果文件权限发生变化,access() 的结果可能会失效,因为它只是检查当前状态。
- 如果文件是符号链接,access() 会检查符号链接指向的目标文件的权限。
返回值
- 成功:返回 0,表示文件存在且具有指定的访问权限。
- 失败:返回 -1,并设置 errno 为以下值之一:
- EACCES:访问被拒绝。
- ENOENT:文件或路径不存在。
- ENOTDIR:路径中的某个部分不是目录。
- EROFS:文件系统只读,无法写入。
注意事项
-
access 函数检查的是 当前进程 的权限,而不是文件的权限位。
-
如果文件是符号链接,access 会检查链接指向的目标文件的权限。
-
在多线程程序中,使用 access 可能会导致竞争条件(race condition),因为文件的状态可能在检查和使用之间发生变化。
示例
#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
// F_OK: check file exist or not
if(access("hello.txt",F_OK) == 0)
{
printf("hello.txt exist\n");
}
else
printf("hello.txt not exist\n");
//检查文件是否有读、写、执行权限
if(access("hello.txt",W_OK | R_OK) == 0)
{
printf("hello.txt have R_OK, W_OK\n");
}
else
printf("hello.txt not have R_OK, W_OK\n");
}
7、unlink 函数
头文件
#include <unistd.h>
函数定义
int unlink(const char *pathname);
参数说明
- pathname:要删除的文件的路径。
返回值
- 成功时返回 0。
- 失败时返回 -1
示例
#include <stdio.h>
#include <unistd.h>
int main()
{
//删除文件文件之前,判断文件是否存在
if(access("hello2.txt",F_OK) == 0)
{
int res = unlink("hello2.txt");
if (res < 0)
{
perror("unlink file error");
return -1;
}
}
}
8、remove 函数
头文件
#include <stdio.h>
函数定义
int remove(const char *pathname);
参数说明
- pathname:要删除的文件的路径。
返回值
- 成功时返回 0。
- 失败时返回 -1
示例
#include <stdio.h>
#include <unistd.h>
int main()
{
//remove 删除空目录
if(access("dir",F_OK) == 0)
{
int res = remove("dir");
if (res < 0)
{
perror("unlink file error");
return -1;
}
}
}
9、fcntl 函数
头文件
#include <unistd.h>
#include <fcntl.h>
函数定义
int fcntl(int fd, int cmd, ... /* arg */ );
参数说明
- fd:文件描述符,表示要操作的文件。
- cmd:操作命令,指定要对文件描述符执行的操作。
- arg:可选参数,具体内容取决于 cmd。
返回值
- 成功时,返回值取决于 cmd。
- 失败时,返回 -1。
常用命令(cmd)
- F_GETFL
:获取文件状态标志(如 O_RDONLY、O_WRONLY、O_NONBLOCK 等)。
int flags = fcntl(fd, F_GETFL);
- F_SETFL
:设置文件状态标志。
fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 设置非阻塞模式
示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fd = STDIN_FILENO;
int flags = fcntl(fd,F_GETFL);//获取原来的flags
flags = flags | O_NONBLOCK; //O_NONBLOCK:非阻塞
fcntl(fd,F_SETFL,flags);//把新的flags设置进去
char buf[128] = "";
// int len = 0;
// while(1)
// {
// len = read(fd,buf,sizeof(buf) - 1);
// printf("len = %d\n",len);
// if(len > 0)
// {
// printf("len = %d buf = %s\n",len,buf);
// break;
// }
// sleep(1);
// }
// 重新改为阻塞
flags = flags^O_NONBLOCK;//异或:相同为0 不同为1
fcntl(fd,F_SETFL,flags);//重新设置新的falgs
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
}
文件锁结构体 struct flock
struct flock {
short l_type; // 锁类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁)
short l_whence; // 起始偏移的基准:SEEK_SET、SEEK_CUR、SEEK_END
off_t l_start; // 起始偏移
off_t l_len; // 锁定长度
pid_t l_pid; // 持有锁的进程 ID(F_GETLK 时有效)
};
示例
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
const char *path = "hello.txt";
int fd = open(path,O_RDWR);
if(fd < 0)
{
perror("open file error");
return -1;
}
struct flock rlock = {0};
rlock.l_type = F_RDLCK;//读锁
rlock.l_whence = SEEK_SET; //文件开头
rlock.l_start = 0;
rlock.l_len = 0; // 0表示锁整个文件
rlock.l_pid = getpid(); //获取当前进程的进程号
int res = fcntl(fd,F_SETLK,&rlock); //设置锁
if(res < 0)
{
perror("fcntl rlock error");
close(fd);
return -1;
}
struct flock lockstat = {0};
fcntl(fd,F_GETLK,&lockstat);
if(lockstat.l_type == F_RDLCK)
printf("set read lock\n");
else if(lockstat.l_type == F_WRLCK)
printf("set write lock\n");
else if(lockstat.l_type == F_UNLCK)
printf("no lock\n");
}
注意
- 文件锁的作用范围:文件锁是基于进程的,不同进程之间的文件锁会相互影响。
- 锁的类型:
- 读锁(F_RDLCK):共享锁,允许多个进程同时获取读锁。
- 写锁(F_WRLCK):互斥锁,只允许一个进程获取写锁。
- 锁的继承:文件锁不会被子进程继承。
示例
写锁互斥锁示例
两个终端运行
读锁
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
//return value加锁成功返回0,失败返回-1
int setlock(int fd,short locktype)
{
struct flock lock = {0};
lock.l_type = locktype; //读锁
lock.l_whence = SEEK_SET; //文件开头
lock.l_start = 0;
lock.l_len = 0; // 0表示锁整个文件
lock.l_pid = getpid(); //获取当前进程的进程号
int res = fcntl(fd,F_SETLK,&lock); //设置锁
// int res = fcntl(fd,F_SETLKW,&lock); //阻塞加锁
if(res < 0)
{
return -1;
}
return 0;
}
int main()
{
const char *path = "hello.txt";
int fd = open(path,O_RDWR);
if(fd < 0)
{
perror("open file error");
return -1;
}
//加锁
// if (setlock(fd,F_RDLCK)<0)
// {
// perror("set lock error");
// close(fd);
// return -1;
// }
while (setlock(fd,F_RDLCK)<0)
{
perror("set lock error");
sleep(1);
}
//读
char buf[128] = "";
read(fd,buf,sizeof(buf) - 1);
printf("buf = %s\n",buf);
getchar();
//解锁
if(setlock(fd,F_UNLCK) < 0)
{
perror("unlock error");
close(fd);
return -1;
}
close(fd);
}
写锁
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
//return value加锁成功返回0,失败返回-1
int setlock(int fd,short locktype)
{
struct flock lock = {0};
lock.l_type = locktype; //读锁
lock.l_whence = SEEK_SET; //文件开头
lock.l_start = 0;
lock.l_len = 0; // 0表示锁整个文件
lock.l_pid = getpid(); //获取当前进程的进程号
int res = fcntl(fd,F_SETLK,&lock); //设置锁
// int res = fcntl(fd,F_SETLKW,&lock); //阻塞加锁
if(res < 0)
{
return -1;
}
return 0;
}
int main()
{
const char *path = "hello.txt";
int fd = open(path,O_RDWR);
if(fd < 0)
{
perror("open file error");
return -1;
}
//加写锁
// if (setlock(fd,F_WRLCK)<0)
// {
// perror("set lock error");
// close(fd);
// return -1; b
// }
while (setlock(fd,F_WRLCK)<0)
{
perror("set lock error");
sleep(1);
}
//写
char buf[128] = "set write lock";
write(fd,buf,strlen(buf));
printf("write success\n");
getchar();
//解锁
if(setlock(fd,F_UNLCK) < 0)
{
perror("unlock error");
close(fd);
return -1;
}
close(fd);
}
读锁共享锁示例
加锁后还能读,另一个进程也可以(新开终端模拟)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
// 设置文件锁
int setlock(int fd, short locktype) {
struct flock lock = {0};
lock.l_type = locktype; // 锁类型(读锁、写锁、解锁)
lock.l_whence = SEEK_SET; // 从文件开头开始
lock.l_start = 0; // 起始偏移量
lock.l_len = 0; // 0 表示锁整个文件
lock.l_pid = getpid(); // 当前进程的进程号
// 设置锁(非阻塞模式)
int res = fcntl(fd, F_SETLK, &lock);
if (res < 0) {
return -1; // 加锁失败
}
return 0; // 加锁成功
}
int main() {
const char *path = "testfile.txt";
int fd = open(path, O_RDWR); // 打开文件
if (fd < 0) {
perror("open file error");
return -1;
}
// 加读锁(共享锁)
printf("Trying to set read lock...\n");
if (setlock(fd, F_RDLCK) < 0) {
perror("set read lock error");
close(fd);
return -1;
}
printf("Read lock set successfully!\n");
// 读取文件内容
char buf[128] = {0};
lseek(fd, 0, SEEK_SET); // 将文件指针移动到开头
read(fd, buf, sizeof(buf) - 1);
printf("File content: %s\n", buf);
// 模拟读操作
printf("Reading file... Press Enter to unlock.\n");
getchar();
// 解锁
printf("Unlocking file...\n");
if (setlock(fd, F_UNLCK) < 0) {
perror("unlock error");
close(fd);
return -1;
}
printf("File unlocked.\n");
close(fd); // 关闭文件
return 0;
}