最近看起了《linux C编程实战》文件操作的部分,看到fcntl函数才发现,之前看过的很多东西不是忘了就是没有理解,所以结合着Stevens大叔的《UNIX环境高级编程》有了更多的了解。
fcntl函数原型:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
函数功能:
fcntl函数可以用来对已打开的文件爱你描述符进行各种控制操作来改变打开文件的各种属性。fcntl 不仅可以施加建议性锁,还可以施加强制锁。同时,还能对文件的某一记录进行上锁,也就是记录锁。
fcntl的功能根据cmd值的不同而不同。
以下是函数的5种功能:
1. 复制一个现有的描述符(cmd=F_DUPFD).
2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
5. 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
关于参数cmd的取值:
1.F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。请参考dup2()。
2.F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
3.F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
4.F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。
5.F_SETFL 设置文件描述词状态旗标,参数arg为新标志。而fcntl的文件状态标志总共有7个:O_RDONLY , O_WRONLY , O_RDWR , O_APPEND , O_NONBLOCK , O_SYNC和O_ASYNC。
可更改的几个标志如下面的描述:
O_NONBLOCK 非阻塞I/O,如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,则read或write调用将返回-1和EAGAIN错误
O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
O_DIRECT 最小化或去掉reading和writing的缓存影响。系统将企图避免缓存你的读或写的数据。如果不能够避免缓存,那么它将最小化已经被缓存了的数据造成的影响。如果这个标志用的不够好,将大大的降低性能
O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候
6.F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回的是负值(arg被忽略)。
7.F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明(arg绝对值的一个进程组ID),否则arg将被认为是进程id。
设置获取文件打开方式示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
// 自定义错误处理函数
void my_err(const char *err_string, int line);
int main(int argc, char *argv[])
{
int ret;
int access_mode;
int fd;
if ((fd = open("example_fc_acc", O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) == -1) {
my_err("open", __LINE__);
}
// 设置文件打开方式
// 以追加的方式写入文件后边
if ((ret = fcntl(fd, F_SETFL, O_APPEND)) < 0) {
my_err("fcntl", __LINE__);
}
// 获取文件打开方式
if ((ret = fcntl(fd, F_GETFL, 0)) < 0) {
my_err("fcntl", __LINE__);
}
// 输出文件打开方式
access_mode = ret & O_ACCMODE;
if (access_mode == O_RDONLY) {
printf("example_fc_acc access mode: read only");
} else if (access_mode == O_WRONLY) {
printf("example_fc_acc access mode: write only");
} else if (access_mode == O_RDWR) {
printf("example_fc_acc access mode: read + write");
}
if (ret & O_APPEND) {
printf(", append");
}
if (ret & O_NONBLOCK) {
printf(", nonblock");
}
if (ret & O_SYNC) {
printf(", sync");
}
printf("\n");
return 0;
}
void my_err(const char *err_string, int line)
{
fprintf(stderr, "line: %d", line);
perror(err_string);
exit(1);
}
运行结果:
*********************************************************************
接下来的3种功能与文件记录锁有关。记录锁的功能是:当一个进程正在读或者修改文件的某个部分时,它可以阻止其他进程修改同一文件区。
当fcntl函数用于管理文件记录锁操作时,第三个参数参数lock指针为flock 结构指针,定义如下:
struct flcok {
short int l_type; /* 锁定的状态*/
//以下的三个参数用于分段对文件加锁,若对整个文件加锁,则:l_whence=SEEK_SET, l_start=0, l_len=0
short int l_whence; /*决定l_start位置*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小*/
pid_t l_pid; /*锁定动作的进程*/
};
l_type 有三种状态:F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
l_len表示锁定区域的长度,若果为0表示从起点(由l_whence和 l_start决定的开始位置)开始直到最大可能偏移量为止。即不管在后面增加多少数据都在锁的范围内。
8.F_GETLK 取得文件锁定的状态。
9.F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
10.F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
利用fcntl函数设置锁示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
// 自定义错误处理函数
void my_err(const char *err_string, int line);
// 锁的设置或释放函数
int lock_set(int fd, struct flock *lock);
// 测试锁 只有当测试发现参数lock制定的锁能够被设置时 返回0
int lock_test(int fd, struct flock *lock);
int main(int argc, char *argv[])
{
int fd;
int ret;
struct flock lock;
char read_buf[32];
// 打开或创建文件
if ((fd = open("example_fc_lock", O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) == -1) {
my_err("open", __LINE__);
}
if (write(fd, "test lock", 10) != 10) {
my_err("write", __LINE__);
}
// 初始化lock结构 锁整个文件
memset(&lock, 0, sizeof(struct flock));
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
// 设置读锁
lock.l_type = F_RDLCK;
if (lock_test(fd, &lock) == 0) {
// 测试可以设置锁
lock.l_type = F_RDLCK;
lock_set(fd, &lock);
}
// 读数据
lseek(fd, 0, SEEK_SET);
if ((ret = read(fd, read_buf, 10)) < 0) {
my_err("read", __LINE__);
}
read_buf[ret] = '\0';
printf("%s\n", read_buf);
// 按任意键
getchar();
// 设置写锁
lock.l_type = F_WRLCK;
if (lock_test(fd, &lock) == 0) {
lock.l_type = F_WRLCK;
lock_set(fd, &lock);
}
// 释放锁
lock.l_type = F_UNLCK;
lock_set(fd, &lock);
close(fd);
return 0;
}
void my_err(const char *err_string, int line)
{
fprintf(stderr, "line:%d ", line);
perror(err_string);
exit(1);
}
int lock_set(int fd, struct flock *lock)
{
// 设置锁
if (fcntl(fd, F_SETLK, lock) == 0) {
if (lock->l_type == F_RDLCK) {
printf("set read lock, pid:%d\n", getpid());
} else if (lock->l_type == F_WRLCK) {
printf("set write lock pid:%d\n", getpid());
} else if (lock->l_type == F_UNLCK) {
printf("release lock, pid:%d\n", getpid());
}
} else {
perror("lock operation fail\n");
return -1;
}
return 0;
}
int lock_test(int fd, struct flock *lock)
{
if (fcntl(fd, F_GETLK, lock) == 0) {
if (lock->l_type == F_UNLCK) {
// 测试可以被设置锁
printf("lock can be set in fd\n");
return 0;
} else {
// 有不兼容的锁的存在,打印出设置该锁的进程
if (lock->l_type == F_RDLCK) {
printf("can't set lock, read lock has been set by: %d\n", lock->l_pid);
} else if (lock->l_type == F_WRLCK) {
printf("can't set lock, write lock has been set by: %d\n", lock->l_pid);
}
return -2;
}
} else {
perror("get incompatible locks fail!\n");
return -1;
}
}
运行结果:
进程23035不能设置写锁因为此时进程23036设置了读锁。
返回值:
fcntl函数的返回值与命令有关。如果出错,所有命令都返回-1,如果成功则返回某个其他值。F_DUPFD、F_GETFD、F_GETFL、F_GETOWN这四个命令有特定返回值,第一个返回新的文件描述符,接下来的两个返回相应标志,最后一个返回一个正的进程ID或负的进程组ID。其他的命令成功则返回0,若有错误则返回-1,错误原因存于errno中。