泰州网站制作平台,四川省住建厅官网,h5网页设计模板,买机箱网站头文件和函数声明
#include unistd.h #include fcntl.h
int fcntl(int fd, int cmd, ... /* arg */ );
函数功能
获取、设置已打开文件的属性
返回值
成功时返回根据 cmd 传递的命令类型的执行结#xff0c;失败时返回 -1#xff0c;并设置 errno 为相…头文件和函数声明
#include unistd.h #include fcntl.h
int fcntl(int fd, int cmd, ... /* arg */ );
函数功能
获取、设置已打开文件的属性
返回值
成功时返回根据 cmd 传递的命令类型的执行结失败时返回 -1并设置 errno 为相对应的错误标志。
参数
fd文件描述符
cmd需要操作的命令类型例如F_GETFL、F_SETFL 等
arg表示要传递的参数具体的含义和 cmd 传递的命令类型有关
1. 用于获取、设置文件的 flags
cmd F_GETFL 时获取打开文件的 flags即调用 open() 函数打开文件传递的 flags 参数。cmd F_SETFL 时设置打开文件的 flags。
在这种情况下fcntl 函数的原型如下所示
int fcntl(int fd, int cmd); // 在获取打开文件的 flags 时
int fcntl(int fd, int cmd, int flags); // 在设置打开文件的 flags 时
例子对于一个 socket创建时默认是阻塞I/O将它设置为非阻塞O_NONBLOCK又设置回阻塞状态。
#include sys/socket.h
#include stdlib.h
#include unistd.h
#include stdio.h
#include fcntl.hvoid err_quit(const char *msg) {perror(msg);exit(EXIT_FAILURE);
}
void close_fd(const int fd, char is_exit) { // is_exit: 0 (false)1trueif (-1 close(fd)) {perror(close error);if (is_exit)exit(EXIT_FAILURE);}
}int main(int argc, char *argv[]) {// 创建socketint fd socket(AF_INET, SOCK_STREAM, 0);if (-1 fd)err_quit(socket error);printf(socket success, fd %d\n, fd);int flags fcntl(fd, F_GETFL);if (-1 flags) {close_fd(fd, 0);err_quit(fcntl error);}printf(before set O_NONBLOCK, flags %d\n, flags);flags | O_NONBLOCK;if (-1 fcntl(fd, F_SETFL, flags)) {close_fd(fd, 0);err_quit(fcntl error);}printf(after set O_NONBLOCK, flags %d\n, flags);// 设置回阻塞状态flags ~O_NONBLOCK;if (-1 fcntl(fd, F_SETFL, flags)) {close_fd(fd, 0);err_quit(fcntl error);}printf(after set ~O_NONBLOCK, flags %d\n, flags);close_fd(fd, 1);return 0;
} 2. 设置文件锁 在 linux 操作系统中当多个进程同时操作同一个文件时该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序如数据库进程有时需要确保它在一个时刻只能被一个进程/线程写这时候就要用到文件锁。文件锁也被称为记录锁record lock 文件锁的作用当一个进程正在读写文件的某一区域时其他进程就不能对文件的这个区域进行修改操作。 提供文件锁操作的函数有两个flock() 和 fcntl()其中flock() 函数是对文件锁操作的早期版本它只能对整个文件加锁不能对文件中的某一个区域加锁。fcntl() 函数是在 flock() 函数的基础上构造出来的它允许对文件中任意字节区域加锁短至一个字节长至整个文件。
在这种情况下fcntl 函数的原型如下所示
int fcntl(int fd, int cmd, struct flock *lock);
flock 结构体定义如下
struct flock {short l_type;short l_whence;off_t l_start;off_t l_len;pid_t l_pid;
};
l_type锁类型F_RDLCK共享读锁、F_WRLCK独占性写锁或 F_UNLCK解锁l_pid持有锁的进程ID。仅由于 cmd F_GETLK 时返回l_len区域字节长度。 l_start参数 l_start 的含义跟参数 l_whence 的值有关 l_whence SEEK_SET 时则将文件的偏移量设置为文件开始处 l_start 个字节参数 l_start 必须为非负数。 l_whence SEEK_CUR 时则将文件的偏移量设置为 当前文件的偏移量 l_start 个字节参数 l_start 可以为正数也可以为负数只要最终得到的文件偏移量不会小于文件的起始位置字节0即可。 l_whence SEEK_END 时则将文件的偏移量设置为 文件长度 l_start 个字节参数 l_start 可以为正数也可以为负数只要最终得到的文件偏移量不会小于文件的起始位置字节0即可。 l_start、l_whence 和 l_len 这三个参数一起指定了待加锁的字节范围。如果 l_len 0则表示锁的范围会被扩展到最大的可能偏移量。这意味着对从由 l_start 和 l_whence 确定的起始位置开始不管向该文件中追加写了多少数据它们都处于锁的范围内。 为了对整个文件加锁可以设置 l_start 0, l_whence SEEK_SET, l_len 0; 锁可以在当前文件尾端处开始或越过文件尾端处开始但不能在文件起始位置之前开始。 一般来说应用程序应该只对所需的最小字节范围进行加锁这样其他进程就能够同时对同一个文件的不同区域进行加锁进而取得更大的并发性。
1cmd F_GETLK 时检测能否获取 lock 指针指定的文件区域的锁lock指针指向的结构中的 l_type 字段的值必须是 F_RDLCK 或 F_WRLCK。如果允许加锁即在指定的文件区域上不存在不兼容的锁那么 l_type 字段会返回 F_UNLCK剩余的字段保持不变。如果在指定的文件区域上存在不兼容的锁即不能加锁那么 lock 会返回不兼容的锁的所有相关信息如果有多把不兼容的锁的话会返回其中一把不兼容的锁的所有相关信息但不确定会返回哪一把 。
2cmd F_SETLK 时给 fd 引用的文件加锁lock 指针指定的文件区域的锁。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁fcntl() 就会失败并返回 -1并设置 errno 为 EAGAIN有的系统也可能设置 errno 为 EACCES。
3cmd F_SETLKW 时这个命令参数是 F_SETLK 命令参数的阻塞版本命令名中的 W 表示等待 wait。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁那么调用进程会设置为休眠。如果请求创建的锁已经可用或者休眠由信号中断该进程将会被唤醒。
需要注意的是用 F_GETLK 测试能否建立一把锁然后用 F_SETLK 或 F_SETLKW 企图建立那把锁这两者不是一个原子操作因此不能保证在这两次 fcntl() 调用之间会不会有另一个进程插入并建立了一把相同的锁。如果不希望在等待锁变成可用时产生阻塞就必须处理 F_SETLK 返回的可能出错。
例子
以下程序功能用 fcntl() 函数对一个对文件进行读写时先加锁为了测试需要读写完后不解锁需要输入 u 命令进行解锁。
#include fcntl.h
#include unistd.h
#include stdlib.h
#include stdio.h
#include string.h
#include errno.h
#include stdarg.h
#include sys/stat.h#define BUF_SIZE 4096/*
is_errno: 是否要打印系统的出错信息0否 1是
is_exit: 打印出错信息后是否立马终止进程0否 1是
*/
void err_msg(const char is_errno, const char is_exit, const char *format, ...);
void close_fd(const int fd, char is_exit);
void cmd_r(const int fd, char* const ibuf, const char *filename);
void cmd_w(const int fd, char* const ibuf, const char *filename);int main(int argc, char *argv[])
{if (argc ! 2)err_msg(0, 1, %s filename, argv[0]);const char *filename argv[1];int fd open(argv[1], O_RDWR | O_APPEND);if (fd -1)err_msg(1, 1, open error);printf(open %s success\n, filename);char ibuf[BUF_SIZE] ; // stdin bufwhile (1) {printf(please input r or w or quit\n, filename);fgets(ibuf, BUF_SIZE, stdin); // 从 stdin 中读入一行if (ibuf[strlen(ibuf) - 1] \n) ibuf[strlen(ibuf) - 1] 0;if (strcmp(ibuf, r) 0) {cmd_r(fd, ibuf, filename);} else if (strcmp(ibuf, w) 0) {cmd_w(fd, ibuf, filename);} else if (strcmp(ibuf, quit) 0) {break;}}close_fd(fd, 1);return 0;
}void err_msg(const char is_errno, const char is_exit, const char *format, ...) {char ibuf[BUF_SIZE] ;va_list ap;va_start(ap, format);vsnprintf(ibuf, BUF_SIZE - 1, format, ap); // 因为最后一个字节放换行符\n所以 BUF_SIZE-1if (is_errno)snprintf(ibuf strlen(ibuf), BUF_SIZE - strlen(ibuf) - 1, : %s, strerror(errno));strcat(ibuf, \n);fflush(stdout); // 刷新stdoutfputs(ibuf, stderr); // 错误信息写到stderrfflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdiova_end(ap);if (is_exit)exit(EXIT_FAILURE);
}void close_fd(const int fd, char is_exit) { // is_exit: 0 (false)1trueif (-1 close(fd)) {perror(close error);if (is_exit)exit(EXIT_FAILURE);}
}void cmd_r(const int fd, char* const ibuf, const char *filename)
{struct flock lock;/* 对整个文件加锁 */lock.l_start 0;lock.l_whence SEEK_SET;lock.l_len 0; lock.l_type F_RDLCK;int res fcntl(fd, F_GETLK, lock);printf(res %d\n, res);if (lock.l_type F_UNLCK) {lock.l_type F_RDLCK;if (fcntl(fd, F_SETLK, lock) -1) {err_msg(0, 0, lock %s fail, please try again later, filename);} else {lseek(fd, 0, SEEK_SET); // 将文件偏移量设置到文件开始处printf(******* 文件内容 *******\n);ssize_t nread 0;char rbuf[BUF_SIZE] ;while ((nread read(fd, rbuf, BUF_SIZE)) 0) {if (write(STDOUT_FILENO, rbuf, nread) ! nread)err_msg(1, 1, write error);}printf(******* 文件内容 *******\n);while (1){printf(%s 已经被该进程共享写锁锁住请输入 u 进行解锁\n, filename);fgets(ibuf, BUF_SIZE, stdin);if (ibuf[strlen(ibuf) - 1] \n)ibuf[strlen(ibuf) - 1] 0;if (strcmp(ibuf, u) 0) {lock.l_type F_UNLCK;if (fcntl(fd, F_SETLK, lock) -1) {err_msg(1, 0, unlock %s fail, please try again later., filename);} else {break;}}}}} else {err_msg(0, 0, getlock %s fail, please try again later, filename);}
}void cmd_w(const int fd, char* const ibuf, const char *filename) {struct flock lock;/* 对整个文件加锁 */lock.l_start 0;lock.l_whence SEEK_SET;lock.l_len 0; lock.l_type F_WRLCK;int res fcntl(fd, F_GETLK, lock);printf(res %d\n, res);if (lock.l_type F_UNLCK) {lock.l_type F_WRLCK;if (fcntl(fd, F_SETLK, lock) -1) {err_msg(0, 0, lock %s fail, please try again later, filename);} else {lseek(fd, 0, SEEK_END); // 将文件偏移量设置到文件末尾char buf[] wrlck\n;size_t len strlen(buf);if (write(fd, buf, len) ! len)err_msg(1, 1, write error);buf[strlen(buf) - 1] 0;printf(%s 已写入文件\n, buf);while (1){printf(%s 已经被该进程独占写锁锁住请输入 u 进行解锁\n, filename);fgets(ibuf, BUF_SIZE, stdin);if (ibuf[strlen(ibuf) - 1] \n)ibuf[strlen(ibuf) - 1] 0;if (strcmp(ibuf, u) 0) {lock.l_type F_UNLCK;if (fcntl(fd, F_SETLK, lock) -1) {err_msg(1, 0, unlock %s fail, please try again later., filename);} else {break;}}}}} else {err_msg(0, 0, getlock %s fail, please try again later, filename);}
}
同时开2个进程测试
1共享读锁进程1读取后未解锁进程2请求读取成功说明读时共享 2独占性写锁进程1写后未解锁进程2请求写请求读都失败说明写时独占 3在文件被多个进程上了共享读锁后需要对这个文件进行读时的所有进程都释放了这把读锁后请求上写锁才会成功。
总结 利用 fcntl() 函数获取、设置文件的 flags 和 设置文件锁是在现实开发中比较常用到的功能fcntl() 函数功能强大简单一言两语比较难比较全面的对这个函数的功能进行解析篇幅较长还有一些其他的功能放在后面的另一篇博文进行更新吧。
参考
《UNIX环境高级编程》(第3版)
《Linux-UNIX系统编程手册》