1 dup()/dup2()
dup()和dup2()都可以复制文件描述符,区别是:
dup()返回的是系统帮忙查找的未使用的最小值
dup2()返回的是第二个参数,如果该值已经被使用,会先关闭然后再使用。
dup() dup2() 复制文件描述符,但不复制对应的文件表。
使用文件描述符时,内存中对应一个文件表,在文件表中,会记录关于内存中文件表的信息和硬盘上的文件的信息,其中,i节点是文件在硬盘上的地址。
dup.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",
O_RDWR|O_TRUNC|O_CREAT,0666);
if(fd==-1) perror("open"),exit(-1);
int fd2 = dup(fd);
printf("fd=%d,fd2=%d\n",fd,fd2);
write(fd,"1",1);
write(fd2,"2",1);
int fd3 = open("b.txt",O_RDWR|O_CREAT,0666);
int fd4 = dup2(fd,fd3);
printf("fd3=%d,fd4=%d\n",fd3,fd4);
write(fd4,"Hehe",4);
close(fd);
close(fd3);
close(fd4);
}
2 lseek()函数
lseek() 是fseek()的底层实现,是系统调用。
第一个参数是fd
第二个参数是偏移量
第三个参数是偏移的起始位置,有3种选择:
SEEK_SET - 从文件头开始
SEEK_CUR - 从当前位置开始
SEEK_END - 从文件尾开始
开发中,更多使用 SEEK_SET。
lseek.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR);
char ch;
read(fd,&ch,1); printf("ch=%c\n",ch);//a
read(fd,&ch,1); printf("ch=%c\n",ch);//b
lseek(fd,3,SEEK_CUR);
read(fd,&ch,1); printf("ch=%c\n",ch);//f
lseek(fd,0,SEEK_SET);write(fd,"1",1);//a->1
lseek(fd,3,SEEK_SET);write(fd,"2",1);//d->2
lseek(fd,2,SEEK_CUR);write(fd,"3",1);//g->3
lseek(fd,-2,SEEK_CUR);write(fd,"4",1);//f->4
/*文件最后用Vi编辑,自动加有"\n",还有文件结束符EOF*/
lseek(fd,-3,SEEK_END);write(fd,"5",1);//s->5
close(fd);
}
在向文件中写时,有缓冲区,并不是我们写了,马上磁盘上就有相应的数据。有相应的函数来设置同步。
writefile.c
#include <stdio.h>
int main(){
int i;
FILE* file = fopen("c.txt","w");
for(i=0;i<10;i++){
fprintf(file,"%d",i);
//fflush(file);
}
sleep(20);
fclose(file);
printf("over\n");
}
3 fcntl()函数
fcntl(int fd,int cmd,…)
1 可以复制文件描述符 (cmd取F_DUPFD)
2 可以获取/设置文件的状态(cmd取F_GETFL/SETFL)
3 实现文件锁(cmd取F_SETLK/F_GETLK/F_SETLKW)
当复制文件描述符时,和dup2类似,不同点:
a 不会强制关闭已经使用的描述符
b 返回值是大于等于第三个参数
3.1 获取/设置文件的状态
获取/设置文件状态时,创建文件的标识不能获取也不能设置(O_CREAT/O_TRUNC/O_EXCL),文件的访问权限标识不能修改。能设置或者修改APPEND标识。
fcntl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",
O_RDONLY|O_CREAT|O_APPEND,0666);
if(fd==-1) perror("open"),exit(-1);
int fd2 = fcntl(fd,F_DUPFD,5);
int fd3 = fcntl(fd,F_DUPFD,5);
printf("fd2=%d,fd3=%d\n",fd2,fd3);//复制
int flags = fcntl(fd,F_GETFL);
printf("flags=%d\n",flags);//没有创建标识
if(flags & O_APPEND) printf("APPEND\n");
//权限标识如何判断?
if((flags&3)==O_RDONLY) printf("RDONLY\n");
fcntl(fd,F_SETFL,O_RDWR);//设置
flags = fcntl(fd,F_GETFL);//再次获取
if(flags & O_APPEND) printf("append\n");
if((flags&3)==O_RDONLY) printf("rdonly\n");
if((flags&3)==O_RDWR) printf("rdwr\n");
close(fd);
close(fd2);
close(fd3);
}
经验:
位与运算经常用于取某一位二进制的值和取某几位二进制的值。比如:取a的最后8位二进制:a&0xFF
最大int:0x7FFFFFFF,最小int:0x80000000
至于为何最小int是0x80000000,见(http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html)
3.2 文件锁
文件锁 - 用来锁定对文件的读写操作
现在主流的锁都是读写锁,由两种锁组成:
1 读锁 是共享锁,锁定其他进程的写操作,允许其他进程读。
2 写锁 是独占锁,锁定其他进程的操作(无论读写)
文件锁对应一个结构,内容:
struct flock{
short l_type; //锁的类型
short l_whence;//锁的开始位置的参照点
int l_start;//锁的开始位置的偏移量
int l_len;//锁定的长度,字节为单位
pid_t l_pid;//加锁的进程ID
};
l_type包括三种:F_RDLCK 读锁 F_WRLCK 写 锁 F_UNLCK 释放锁
l_whence和l_start 联合决定锁定的开始位置,比如:l_whence = SEEK_SET l_start=10;从文件开始第10个字节上锁。
l_pid只在F_GETLK时有效,其他时候置 -1 即可。
lock1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd==-1) perror("open"),exit(-1);
struct flock lock;
lock.l_type = F_WRLCK/*F_RDLCK*/;//读锁
lock.l_whence = SEEK_SET;
lock.l_start = 10;
lock.l_len = 20;
lock.l_pid = -1;//一般给-1
int res = fcntl(fd,F_SETLK,&lock);
if(res == -1) perror("lock");
else printf("lock ok\n");
sleep(20);//lock2不需要sleep
close(fd);
}
lock2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR);
if(fd==-1) perror("open"),exit(-1);
struct flock lock;
lock.l_type = F_WRLCK;//写锁
lock.l_whence = SEEK_SET;
lock.l_start = 10;
lock.l_len = 20;
lock.l_pid = -1;//一般给-1
int res = fcntl(fd,F_SETLK,&lock);
if(res == -1) perror("lock");
else printf("lock ok\n");
close(fd);
}
编译上面两个程序:gcc lock1.c -oa,gcc lock2.c -ob,先运行a,加上读锁,再运行b,可以加上读锁,但是加上写锁就不行。
上面的两个程序都是程序结束后,自动释放锁,下面在程序中来实现解锁。
lock3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd==-1) perror("open"),exit(-1);
struct flock lock;
lock.l_type = F_RDLCK;//读锁
lock.l_whence = SEEK_SET;
lock.l_start = 10;
lock.l_len = 20;
lock.l_pid = -1;//一般给-1
int res = fcntl(fd,F_SETLK,&lock);//加锁
if(res == -1) perror("lock");
else printf("lock ok\n");
sleep(20);//lock2不需要sleep
printf("文件读完了\n");
lock.l_type = F_UNLCK;
res = fcntl(fd,F_SETLK,&lock);//释放锁
if(res == -1) perror("release");
else printf("release ok\n");
sleep(15);//模拟读完文件后作的事情
close(fd);
}
文件锁不能锁定硬盘上的文件,不能锁定read/write函数,只能阻止其他进程的上锁行为。
文件锁的正确用法:在调用读函数read()之前应该上读锁,在调用写函数之前上写锁。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR);
if(fd==-1)
perror("open"),exit(-1);
struct flock wlock;
wlock.l_type = F_WRLCK;
wlock.l_whence = SEEK_SET;
wlock.l_start = 0;
wlock.l_len = 20;
wlock.l_pid = -1;
int res = fcntl(fd,F_SETLK,&wlock);//先上锁
if(res==-1)
perror("lock");
else//再调写函数
write(fd,"hello worldabcdefg",18);
close(fd);
}
使用文件锁时,其他进程默认是加不上锁会返回错误,可以用F_SETLKW实现加不上锁继续等待的效果。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR);
if(fd==-1)
perror("open"),exit(-1);
struct flock wlock;
wlock.l_type = F_WRLCK;
wlock.l_whence = SEEK_SET;
wlock.l_start = 0;
wlock.l_len = 20;
wlock.l_pid = -1;
int res = fcntl(fd,F_SETLKW,&wlock);//先上锁
if(res==-1)
perror("lock");
else//再调写函数
write(fd,"hello worldabcdefg",18);
close(fd);
}
fcntl的cmd为F_GETLK时,不是获取锁,而是测试某个锁能否加上,不会真正的加锁。
如果锁可以加,调用F_GETLK的结果:
struct flock的其他数据不变,锁的类型l_type变为F_UNLCK。
如果锁不可以加,调用F_GETLK的结果:
struct flock的数据都会发生改变,锁的类型、锁的l_whence、l_start、l_len变成 当前正在起作用的锁的相关信息,l_pid 变成当前正在加锁的进程ID。(不是测试的进程)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
int fd = open("a.txt",O_RDWR);
if(fd==-1)
perror("open"),exit(-1);
struct flock rlock;
rlock.l_type = F_RDLCK;
rlock.l_whence = SEEK_SET;rlock.l_start = 0;
rlock.l_len = 30; rlock.l_pid = -1;
int res = fcntl(fd,F_GETLK,&rlock);//测试锁
printf("%d,%d,%d\n",F_RDLCK,F_WRLCK,F_UNLCK);
if(res==-1)
perror("rlock"),exit(-1);
else
printf("type=%d,start=%d,l_pid=%d\n",
rlock.l_type,rlock.l_start,rlock.l_pid);
rlock.l_type = F_WRLCK;//写锁
res = fcntl(fd,F_GETLK,&rlock);//测试锁
if(res==-1)
perror("rlock"),exit(-1);//
else{
printf("type=%d,start=%d,l_pid=%d\n",
rlock.l_type,rlock.l_start,rlock.l_pid);
}
}