目录
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd):
dup
函数复制oldfd
参数所指向的文件描述符。参数:
oldfd
:要复制的文件描述符的整数值。返回值: 返回新的文件描述符,如果出错,则返回 -1,并设置
errno
。
新的文件描述符和oldfd共享所有的锁定,读写指针和各项权限和标志位,如果用lseek对某个文件描述符操作,另一个文件描述符的读写指针也会跟着变动
dup2
函数将newfd
参数指定的文件描述符设置为oldfd
参数指定的文件描述符的副本。参数:
oldfd
:要复制的文件描述符的整数值。newfd
:要设置为副本的新文件描述符的整数值。返回值: 返回新的文件描述符(即
newfd
),如果出错,则返回 -1,并设置errno
。示例
#include <stdio.h> #include <unistd.h> int main() { int fd1 = open("file.txt", O_RDONLY); int fd2 = open("newfile.txt", O_WRONLY | O_CREAT, 0644); dup2(fd1, 10); // 将文件描述符 10 设置为 fd1 的副本 dup2(fd2, 11); // 将文件描述符 11 设置为 fd2 的副本 // 现在文件描述符 10 与 fd1 指向相同的文件,文件描述符 11 与 fd2 指向相同的文件 close(fd1); close(fd2); return 0; }
若newfd已经被程序使用,系统会将其关闭以释放该文件描述符;若newfd==oldfd,则不会关闭该文件。
#include <unistd.h>
#include <fcntl.h>
int fentl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
功能根据cmd的值的不同而不同,失败一般返回-1
复制文件描述符(
F_DUPFD
):
int new_fd = fcntl(old_fd, F_DUPFD, 0);
这个命令用于复制文件描述符
old_fd
,并返回一个新的文件描述符new_fd
在
fcntl
函数的F_DUPFD
命令中,第三个参数(常被称为arg
)是用于指定新的文件描述符的最小值。这个参数告诉系统在哪里开始搜索未使用的文件描述符来复制。如果指定的值是 0,系统会选择一个未使用的最小文件描述符号。如果你希望指定一个特定的文件描述符,可以传递你想要的值。
F_GETFD
:
int flags = fcntl(fd, F_GETFD);
用于获取文件描述符
fd
的标志,例如 close-on-exec 标志。如果
FD_CLOEXEC
标志被设置,说明文件描述符在exec
被调用时会被关闭。否则,说明文件描述符会保持打开状态。可以这样判断
int flags = fcntl(fd, F_GETFD); if (flags & FD_CLOEXEC) { printf("FD_CLOEXEC flag is set.\n"); } else { printf("FD_CLOEXEC flag is not set.\n"); }
F_SETFD
:
fcntl(fd, F_SETFD, FD_CLOEXEC);//比如arg是FD_CLOEXEC
用于设置文件描述符
fd
的标志,例如设置 close-on-exec 标志。
F_GETFL
:
int flags = fcntl(fd, F_GETFL);
用于获取文件描述符
fd
的文件状态标志,如读写模式和非阻塞标志。
F_SETFL
:
fcntl(fd, F_SETFL, O_NONBLOCK);
用于设置文件描述符
fd
的文件状态标志,如设置非阻塞标志。Linux系统只能设置O_APPEND,O_NONBLOCK,O_ASYNC,含义与open函数里的一致
演示fcntl的一些功能
#include <cstdio>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
//自定义错误处理函数
void my_err(const char *err_string,int line){
fprintf(stderr,"line:%d ",line);
perror(err_string);
exit(1);
}
int main(){
int ret;
int access_mode;
int fd;
if((fd=open("exanple_64",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1)
my_err("open",__LINE__);
//设置文件打开方式
if((ret=fcntl(fd,F_SETFL,O_APPEND))==-1)
my_err("fcntl",__LINE__);
//获取文件打开方式
if((ret=fcntl(fd,F_GETFL,0))==-1)//0可以去掉
my_err("fcntl",__LINE__);
access_mode=ret&O_ACCMODE;
if(access_mode==O_RDONLY)
printf("example_64 access mode:read only");
else if(access_mode==O_WRONLY)
printf("example_64 access mode:write only");
else if(access_mode==O_RDWR)
printf("example_64 access mode:read+write");
if(ret&O_APPEND)
printf(" ,append");
if(ret&O_NONBLOCK)
printf(",nonblock");
if(ret&O_SYNC)
printf(",sycn");
printf("\n");
return 0;
}
运行结果是
结果解释:虽然open的时候设置的是O_TRUNC,是将文件截断为0,但是后面的代码用fcntl修改打开方式为O_APPEND,所以O_APPEND是存在的。(不过效果依然是截断,文件会清空。因为open的时候设置的是截断)
ret&O_ACCMODE是取得文件打开方式的掩码,它其实就是3 。因为文件打开方式有三种,是2位的,所以需要&11(二进制),也就是3 。
if(ret & O_APPEND)
这句代码用于检查文件状态标志中是否设置了 O_APPEND
标志。
接下来的fcntl函数3种功能都和文件记录锁有关,因此先介绍一下文件记录锁。
当有多个进程同时对某一文件进行操作时,就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。但是对于有些应用程序,如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,Linux系统提供了记录锁机制。
Linux 的文件记录锁能提供非常详细的控制,它能对文件的某一区域进行文件记录锁的控制。当fentl用于管理文件记录锁的操作时,第三个参数指向一个struct flock *lock的结构:
struct flock {
short l_type; /* 锁的类型: F_RDLCK(共享读锁)、F_WRLCK(独占写锁)、F_UNLCK(释放锁) */
short l_whence; /* l_start 的解释方式: SEEK_SET(相对于文件的起始位置)、SEEK_CUR(相对于当前文件位置)、SEEK_END(相对于文件的末尾) */
off_t l_start; /* 锁的起始位置,即锁定范围的起始偏移量 */
off_t l_len; /* 锁定的字节数,即锁定范围的长度 */
pid_t l_pid; /* 执行 F_GETLK 命令时,包含持有锁的进程的进程ID */
};
l_type
:锁的类型,可以是以下之一:
F_RDLCK
:共享读锁。F_WRLCK
:独占写锁。F_UNLCK
:释放锁。
l_whence
:指定l_start
的解释方式,可以是以下之一:
SEEK_SET
:相对于文件的起始位置。SEEK_CUR
:相对于当前文件位置。SEEK_END
:相对于文件的末尾。
l_start
:锁的起始位置,即锁定范围的起始偏移量。
l_len
:锁定的字节数,即锁定范围的长度。
l_len
字段为0通常表示锁定或解锁整个文件。
l_pid
:在执行F_GETLK
命令时,将包含持有锁的进程的进程ID。
多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上的写锁则只能由一个进程单独使用。进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁(锁的不兼容规则)。一个进程只能设置某一文件区域上的一种锁。如果某一文件区域已经存在文件记录锁了,则如果此时再设置新的锁在该区域的话,旧的锁将会被新的锁取代。
为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0。
F_GETLK
- 获取文件锁信息:
struct flock lock;
fcntl(fd, F_GETLK, &lock);
此命令用于获取文件描述符
fd
上的锁信息,这些信息会被存储在struct flock
结构体中。通过检查结构体中的字段,可以了解关于文件锁的信息,例如锁定的类型、锁定范围等。
F_SETLK
- 设置文件锁:
fcntl(fd, F_SETLK, &lock);
此时,fcntl系统调用被用来设置或释放锁,当l_type取 F_RDLCK 或F_WDLCK时,在由l_whence、l_ start和l_len指定的区域上设置锁;当l_type取F_UNLCK时则释放锁。如果锁被其他进程占用,则返回-1并设置errno为 EACCES 或EAGAIN。
需要注意的是,当设置一个共享锁(读锁)时,第一个参数fd所指向的文件必须以可读方式打开;当设置一个互斥锁(写锁)时,第一个参数fd所指向的文件必须以可写方式打开;当设置两种锁时,第一个参数fd所指向的文件必须以可读可写方式打开。当进程结束或文件描述符fd被close系统调用时,锁会自动释放。
F_SETLKW
- 设置文件锁并等待:
fcntl(fd, F_SETLKW, &lock);
此命令与
F_SETLK
类似,但是如果无法获取所请求的锁,F_SETLKW
会使调用进程进入阻塞状态,直到可以获取锁为止。这可以用于在获取锁之前等待其他进程释放锁。
成功返回0,失败返回-1。
#include<iostream>
#include<cstring>
#include <cstdio>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
//自定义错误处理函数
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){//set success
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",getpid());
}
else{//fail
perror("lock operation fail\n");
return -1;
}
return 0;
}
//test,only success return 0
int lock_test(int fd,struct flock *lock){
if(fcntl(fd,F_GETLK,lock)==0){//success
if(lock->l_type==F_UNLCK){//释放可行
printf("lock can be set in fd");
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 be set lock,write lock has been set by:%d\n",lock->l_pid);
return -2;
}
}else{
perror("get incompatible locks fail");
return -1;
}
}
int main(){
int fd;
int ret;
struct flock lock;
char read_buf[32];
if((fd=open("example_65",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1)
my_err("open",__LINE__);
if(write(fd,"test lock",10)!=10)
my_err("write",__LINE__);
//init lock
memset(&lock,0,sizeof(struct flock));
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len=0;
//set read lock
lock.l_type=F_RDLCK;
if(lock_test(fd,&lock)==0){//test succeeded
lock.l_start=0;
lock.l_whence=SEEK_SET;
lock.l_len=0;
lock.l_type=F_RDLCK;
lock_set(fd,&lock);
}
//read data
lseek(fd,0,SEEK_SET);
if((ret=read(fd,read_buf,10))==-1)
my_err("read",__LINE__);
read_buf[ret]='\0';
printf("%s\n",read_buf);
getchar();
//set write lock
lock.l_type=F_WRLCK;
if(lock_test(fd,&lock)==0){
lock.l_start=0;
lock.l_whence=SEEK_SET;
lock.l_len=0;
lock.l_type=F_WRLCK;
lock_set(fd,&lock);
}
//release
lock.l_type=F_UNLCK;
lock_set(fd,&lock);
close(fd);
return 0;
}
运行结果:
结果解释:首先测试能不能在文件上加读锁,测试成功,所以加了读锁,然后读文件内容为:test lock。之后测试能不能加写锁,发现可以,就加了写锁。最后释放锁。
注意这是在同一个进程内的,所以先加读锁再加写锁,会覆盖写锁。但是如果是不同进程就不可以先加读锁再加写锁。
为了演示锁的不兼容性,这次程序在不同终端执行
再在另一个终端执行
可以看到两个读锁都可以设置
这时候在第二个终端任意按一个键,按理说接下来要设置写锁,但是
失败了,因为进程1已经设置了读锁,进程2就不能设置写锁了。
现在,既然进程2已经释放锁了,我们回到进程1按下按键试试
因为进程2的锁已经释放了,只有进程1本进程的读锁,同一进程可以先读锁后写锁,所以成功。
fcntl还有一些功能如下图
#include <sys/ioctl.h>
int ioctl (int fd, int request,..)
我暂时也看不懂示例程序,所以这个函数就跳过吧