指定文件描述符
#include <unistd.h>
int dup2(int oldfd, int newfd);
->功能:复制文件描述符表的特定条目到指定项,
->参数:oldfd: 源文件描述符
newfd: 目标文件描述符
->返回值:成功返回目标文件描述符(newfd),失败返回-1。
->dup2函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制。
代码实现
//文件描述符的复制
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
//打开文件,得到文件描述符oldfd
int oldfd = open("./dup.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(oldfd == -1){
perror("open");
return -1;
}
printf("oldfd = %d\n", oldfd);
//通过复制文件描述符oldfd,得到新的文件描述符newfd
int newfd = dup2(oldfd,STDOUT_FILENO);
if(newfd == -1){
perror("dup2");
return -1;
}
printf("newfd = %d\n", newfd);
//通过oldfd向文件写入数据,hello world!
if(write(oldfd, "hello world!", 12) == -1){
perror("write");
return -1;
}
//通过newfd修改文件读写位置lseek
if(lseek(newfd, -6, SEEK_END) == -1){
perror("lseek");
return -1;
}
//通过oldfd向文件写入数据,hello linux!
char* buf = "linux!";
if(write(oldfd, buf, 6) == -1){
perror("write");
return -1;
}
//关闭文件
close(oldfd);
close(newfd);
return 0;
}
执行结果
oldfd = 3
[1] + Done "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-3jfxpdbu.c5g" 1>"/tmp/Microsoft-MIEngine-Out-gykxl3f2.tnf"
day03$cat dup.txt
newfd = 1
hello linux!day03$
访问测试
#include <unistd.h>
int access(char const* pathname, int mode);->功能:判断当前进程是否可以对某个给定的文件执行某中访问
->参数:pathname 文件路径
mode被测试权限,可从一下取值
R_OK -可读否
W_OK -可写否
X_OK -可执行否
F_OK -存在否
->返回值:成功返回0,失败返回-1
代码实现(从终端获取输入)
#include<stdio.h>
#include<unistd.h>
int main(int argc,char*argv[]){
if(argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
if(access(argv[1],F_OK) == -1){
printf("文件%s不存在\n",argv[1]);
return 0;
}
int readable = access(argv[1],R_OK) == 0;
int writable = access(argv[1],W_OK) == 0;
int executable = access(argv[1],X_OK) == 0;
printf("文件%s", argv[1]);
if(readable){
printf("可读,");
}else{
printf("不可读,");
}
if(writable){
printf("可写,");
}else{
printf("不可写,");
}
if(executable){
printf("可执行\n");
}else{
printf("不可执行\n");
}
return 0;
}
执行结果
day03$./access dup.txt
文件dup.txt可读,可写,不可执行
day03$./access qqq.txt
文件qqq.txt不存在
修改文件大小
#include <unistd.h>
int truncate(char const* path, off_t length);int ftruncate(int fd, off_t length);
->功能:修改指定文件的大小
->参数:path 文件路径
length 文件大小fd 文件描述符
->返回值:成功返回0,失败返回-1。
->该函数既可以把文件截短,也可以把文件加长,所有的改变均发生在文件的尾部,新增加的部分用数字0填充。
代码实现
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main(){
//打开文件
int fd = open("./trunc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
if(fd == -1){
perror("open");
return -1;
}
//向文件写入数据abcde 5
char buf[] = "abcde";
if(write(fd, buf, strlen(buf)) == -1){
perror("write");
return -1;
}
//修改文件大小 abc 3
if(truncate("./trunc.txt", 3) == -1){
perror("ftruncate");
return -1; //执行结果:abc
}
//再次修改文件大小 5
if (ftruncate(fd, 5) == -1){
perror("ftruncate");
return -1; //执行结果:abc^@^@
}
//关闭文件
close(fd);
return 0;
}
文件锁
为了避免多个进程在读写同一个文件的同一个区域时发生冲突,Unix/Linux系统引入了文件锁机制,并把文件锁分为读锁和写锁两种,它们的区别在于,对一个文件的特定区域可以加多把读锁,对一个文件的特定区域只能加一把写锁
基于锁的操作模型是:读/写文件中的特定区域之前,先加上读/写锁,锁成功了再读/写,读/写完成以后再解锁
当通过close函数关闭文件描述符时,调用进程在该文件描述符上所加的一切锁将被自动解除;
当进程终止时,该进程在所有文件描述符上所加的一切锁将被自动解除。
读写冲突
- 如果两个或两个以上的进程同时向一个文件的某个特定区域写入数据,那么最后写入文件的数据极有可能因为写操作的交错而产生混乱
- 如果一个进程写而其它进程同时在读一个文件的某个特定区域,那么读出的数据极有可能因为读写操作的交错而不完整
代码演示
写入冲突
//写入冲突演示
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[]){
//打开文件
int fd = open("./conflict.txt",O_WRONLY | O_CREAT |O_APPEND,0664);
if(fd == -1){
perror("open");
return -1;
}
//写入数据
for(int i = 0;i < strlen(argv[1]);i++){
if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
perror("write");
return -1;
}
sleep(1);
}
//关闭文件
close(fd);
return 0;
}
同时执行,分别执行写入hello和world,接下来看写入结果
写入结果
hewlolrold
文件锁的使用
#include <fcntl.h>
int fcntl(int fd,F_SETLK/F_SETLKW, struct flock* lock);
->功能:加解锁
->参数:F_SETLK 非阻塞模式加锁,F_SETLKV,阳塞模式加锁
lock 对文件要加的锁
->返回值:成功返回0,失败返回-1struct flock {
short l_type; //锁类型:F _RDLCK/F_WRLCK/F_UNLCKshort l_whence; // 锁区偏移起点:SEEK_SET/SEEK_CUR/SEEK_END
off_t I_start; // 锁区偏移字节
off_t l_len; // 锁区字节数
pid_t I_pid;//加锁进程的PID,-1表示自动设置};
通过对该结构体类型变量的赋值,再配合fcntl函数,以完成对文件指定区域的加解锁操作
阻塞模式加锁
代码实现
//写入冲突演示
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main(int argc,char* argv[]){
//打开文件
int fd = open("./conflict.txt",O_WRONLY | O_CREAT |O_APPEND,0664);
if(fd == -1){
perror("open");
return -1;
}
//加锁
struct flock lock;
lock.l_type = F_WRLCK;//写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
if(fcntl(fd,F_SETLKW,&lock) == -1){
perror("fcntl");
return -1;
}
//写入数据
for(int i = 0;i < strlen(argv[1]);i++){
if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
perror("write");
return -1;
}
sleep(1);
}
//解锁
struct flock unlock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
if(fcntl(fd,F_SETLK,&unlock) == -1){
perror("fcntl");
return -1;
}
//关闭文件
close(fd);
return 0;
}
执行结果
hewlolrold
helloworld
非阻塞模式加锁
代码实现
//写入冲突演示
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
int main(int argc,char* argv[]){
//打开文件
int fd = open("./conflict.txt",O_WRONLY | O_CREAT |O_APPEND,0664);
if(fd == -1){
perror("open");
return -1;
}
//加锁
struct flock lock;
lock.l_type = F_WRLCK;//写锁
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
// if(fcntl(fd,F_SETLKW,&lock) == -1){
// perror("fcntl");
// return -1;
// }
//非阻塞加锁
while(fcntl(fd,F_SETLK,&lock) == -1){
if(errno == EACCES || errno == EAGAIN){
printf("文件被锁定,请等待\n");
sleep(1);
}else{
perror("fcntl");
return -1;
}
}
//写入数据
for(int i = 0;i < strlen(argv[1]);i++){
if(write(fd,&argv[1][i],sizeof(argv[1][i])) == -1){
perror("write");
return -1;
}
sleep(1);
}
//解锁
struct flock unlock;
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;//一直锁到文件尾
lock.l_pid = -1;
if(fcntl(fd,F_SETLK,&unlock) == -1){
perror("fcntl");
return -1;
}
//关闭文件
close(fd);
return 0;
}
窗口一执行结果
day03$./wlock hello
day03$
窗口二执行结果
day03$./wlock world
文件被锁定,请等待
文件被锁定,请等待
文件被锁定,请等待
文件被锁定,请等待
文件被锁定,请等待
day03$cat conflict.txt
hewlolrold
helloworldhelloworldday03$
文件锁的内核结构
每次对给定文件的特定区域加锁,都会通过fcntl函数向系统内核传递flock结构体,该结构体中包含了有关锁的一切细节,诸如锁的类型(读锁/写锁),锁区的起始位置和大小,甚至加锁进程的PID(填-1由系统自动设置) 系统内核会收集所有进程对该文件所加的各种锁,并把这些flock结构体中的信息,以链表的形式组织成一张锁表,而锁表的起始地址就保存在该文件的v节点中
任何一个进程通过fcntl函数对该文件加锁,系统内核都要遍历这张锁表,旦发现有与欲加之锁构成冲突的锁即阻塞或报错,否则即将欲加之锁插入锁表而解锁的过程实际上就是调整或删除锁表中的相应节点