文件描述符的文件操作(非缓冲)

文件描述符:
内核为每个进程维护一个已打开文件的记录表,文件描述符是一个较小的正整数(0—1023),它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读、写、创建、删除等操作。

1、打开,创建,关闭文件
open 和 creat 都能打开和创建函数,原型为

#include <sys/types.h> //头文件
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags); //文件名 打开方式
int open(const char *pathname, int flags, mode_t mode); //文件名 打开方式 权限
int creat(const char *pathname, mode_t mode); //文件名 权限 //现在已经不常用了
creat 函数等价于 open(pathname,O_CREAT|O_TRUNC|O_WRONLY,mode);
int close(int fd);//fd 表示文件描述符

在这里插入图片描述
在这里插入图片描述
2、文件的读写

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);//文件描述词 缓冲区 长度
ssize_t write(int fd, const void *buf, size_t count);

对于读写文件的阻塞和非阻塞的问题
比如:read(fd,buf,bufsiz)从fd中取bufsiz个字节到buf数组,在此期间可以认为是堵塞的,但是能快就能返回,当fd是交互的io,比如STDIN_FILENO时,这认为是一个低速系统调用,可能会堵塞很长时间,所以一般要对这种可能会堵塞长时间的低速系统调用进行定时控制

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
main()
{
	char buf[512]={"111"};
	int flags = fcntl(STDIN_FILENO,F_GETFL);//1. 获取标准输入的状态flags 
	printf("%d\n",flags);
	flags |= O_NONBLOCK; //2. 重新设置状态旗标。此处表示非阻塞输入
	fcntl(STDIN_FILENO,F_SETFL,flags); //3. 将新的状态旗标设置进去
	read(STDIN_FILENO,buf,512);
	puts(buf);
	printf("\n--------------------------------------------------\n");
	flags ^=O_NONBLOCK;
	fcntl(STDIN_FILENO,F_SETFL,flags); //4. 通过异或清除非阻塞标志位
	read(STDIN_FILENO,buf,512);
	puts(buf);
	
}

在这里插入图片描述
如果设置非阻塞的话,很快就返回了,但是如果设置的是阻塞状态的话,他就是要等待用户进行输入才行

2.获取文件信息

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf); //文件名 stat
int fstat(int fd, struct stat *buf); //文件描述词 stat 结构体指针
int lstat(const char *file_name, struct stat *buf);
lstat 函数类似于 stat, 但是当命名的文件是一个符号链接时,lstat 得到该符号链接文件的有关信息,而不是该符号链接指向的文件的信息。结构体 stat 的定为:
struct stat {
dev_t st_dev; /*如果是设备,返回设备表述符,否则为 0*/
ino_t st_ino; /* i 节点号 */
mode_t st_mode; /* 文件类型 */
nlink_t st_nlink; /* 链接数 */
uid_t st_uid; /* 属主 ID */
gid_t st_gid; /* 组 ID */
dev_t st_rdev; /* 设备类型*/
off_t st_size; /* 文件大小,字节表示 */
blksize_t st_blksize; /* 块大小*/
blkcnt_t st_blocks; /* 块数 */
time_t st_atime; /* 最后访问时间*/
time_t st_mtime; /* 最后修改时间*/
time_t st_ctime; /* 创建时间 */
};

在这里插入图片描述
3.检测权限函数
int access(const char *pathname, int mode);//要检测的文件名 要检测的权限
成功返回 0,否则返回-1
Linux 下不同的用户对文件的可操作权限不同,access 函数可测试当前用户(运行该函数程序的用户)对某文件是否有相关权限。其是按实际用户 ID 和实际组 ID 进行权限许可测试的。mode 可以是以下宏:R_OK 读 W_OK 写 X_OK 执行 F_OK 测试文件是否存在

void quanxian(char *filename)
{
	if(!access(filename, F_OK)){	
		if(!access(filename, R_OK)){
			printf("r");
		}else{
			printf("-");
		}
		if(!access(filename, W_OK)){
			printf("w");
		}else{
			printf("-");
		}
		if(!access(filename, X_OK)){
			printf("x");
		}else{
			printf("-");
		}
	}else{
		printf("file not exist!\n");
	}
}

4.标准输入输出文件描述符
与标准的输入输出流对应,在更底层的实现是用标准输入、标准输出、标准错误文件描述符表示的。它们分别用STDIN_FILENOSTDOUT_FILENO 和 STDERR_FILENO 三个宏表示,值分别是 0、1、2 三个整型数字。
标准输入文件描述符  STDIN_FILENO  0
标准输出文件描述符  STDOUT_FILENO  1
标准错误输出文件描述符  STDERR_FILENO  2

5.时间和日期相关函数
日历时间:用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器会有所不同。在 Linix 系统中,这个标准时间点是 1970 年 1 月 1 日 00:00:00。用 time_t 这种数据类型来表示从
那一刻到现在所经过的秒数。

#inlcude <time.h>
time_t time(time_t *t);
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
time_t:保存日历时间的数据类型 tm:保存时间的结构体
struct tm 结构如下:
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
获取系统时间并打印
#include <stdio.h>
#include <time.h>
main()
{
/*获取日历时间, 从 1970 -1 -1 0 点到现在的秒数。*/
time_t tt = time(NULL);
printf("%d\n", tt);
struct tm *tm;
/*将日历时间转换为格林威治时间(世界标准时间)*/
tm = gmtime(&tt);
printf("tm_hour = %d\n", tm->tm_hour);
/*转换为本地时间*/
tm = localtime(&tt);
printf("%d: %d: %d\n", tm->tm_hour, tm->tm_min, tm->tm_sec);
return 0;
}

6、文件的定位

函数 lseek 将文件指针设定到相对于 whence,偏移值为 offset 的位置
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);//fd 文件描述词
whence 可以是下面三个常量的一个
SEEK_SET 从文件头开始计算
SEEK_CUR 从当前指针开始计算
SEEK_END 从文件尾开始计算

利用该函数可以实现文件空洞(对一个新建的空文件,可以定位到偏移文件开头 1024 个字节的地方,在写入一个字符,则相当于给该文件分配了 1025 个字节的空间,形成文件空洞)通常用于多进程间通信的时候的共享内存

int main()
{
int fd = open("c.txt", O_WRONLY | O_CREAT);
lseek(fd, 1024, SEEK_SET);
write(fd, "a", 1);
close(fd);
return 0;
}

7、原子操作
如果对文件的读写操作很重要,不能被打断,可以用原子操作函数。
对文件的读写原子操作有 perad、pwrite。
ssize_t pread(int fd, void *buf, size_t count, off_t offset); //文件描述符,缓冲区长度,相对于文件头的偏移量
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

8、文件描述符的赋值

函数 dup 和 dup2 可以实现文件描述符的复制,此时文件描述符是没有打开的最小文件描述符。原型为:
#include <unistd.h> //头文件包含
int dup(int oldfd);
int dup2(int oldfd, int newfd);

文件描述符的复制是指用另外一个文件描述符指向同一个打开的文件,它完全不同于直接给文件描述符变量赋
值,例如:
描述符变量的直接赋值:
char szBuf[32];
int fd=open(“./a.txt”,O_RDONLY);
int fd2=fd; //类似于 C 语言的指针赋值,当释放掉一个得时候,另一个已经不能操作了
close(fd); //导致文件立即关闭
printf(“read:%d\n”,read(fd2),szBuf,sizeof(szBuf)-1); //读取失败
close(fd2); //无意义
在此情况下,两个文件描述符变量的值相同,指向同一个打开的文件,但是内核的文件打开引用计数还是为 1,
所以 close(fd)或者 close(fd2)都会导致文件立即关闭掉。
描述符的复制:
char szBuf[32];
int fd=open(“./a.txt”,O_RDONLY);
int fd2=dup(fd); //内核的文件打开引用计算+1,变成 2 了
close(fd); //当前还不会导致文件被关闭,此时通过 fd2 照样可以访问文件
printf(“read:%d\n”,read(fd2),szBuf,sizeof(szBuf)-1);
close(fd2); //内核的引用计数变为 0,文件正式关闭
此时 fd2 如果修改了文件内容,则文件内容将会改变。即 fd 和 fd2 中有一个修改文件之后对应的另一个也变了。

int main(int argc,char *argv[])
{
	char szBuf[32]={0};
	int fda=open("./a.txt",O_RDWR); //假设 a.txt 的内容为:abcdef
	int fdaa=dup(fda);
	read(fda,szBuf,sizeof(szBuf)-1);
	puts(szBuf); //关闭之前先输入原来的内容
	close(fda);
	printf("\n");
	write(fdaa,"hellojava",9); //再往里面写内容
	lseek(fdaa, 0, SEEK_SET);
	read(fdaa,szBuf,sizeof(szBuf)-1);
	puts(szBuf); //输出现在的内容
	close(fdaa);
}

解析:假设 a.txt 中的内容为:abcdef.上面的例子会发现第一次输出的结果是 abcdef。关闭 close(fda)的时候,文
件实际上还没有真正的关闭,此时文件指针已经向后移动了。执行 write 命令将 hellojava 又追加到 a.txt 的后面,
最后关闭 fdaa 的时候,输出:abcdef hellojava。

9.文件的锁定

在多进程对同一个文件进行读写访问时,为了保证数据的完整性,有时要对文件进行锁定。可以通过 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);

返回值 成功则返回 0,若有错误则返回-1
参数 struct flock 为结构体,表示文件锁信息,如下所示:
struct flock
{
	short int l_type; /*锁定的状态*/ 用 F_RDLCK 和 F_WRLCK 和 F_UNLCK
	short int l_whence; /*决定 l_start 位置*/SEEK_SETSEEK_CURSEEK_END
	off_t l_start; /*锁定区域的开头位置*/
	off_t l_len; /*锁定区域的大小*/struct stat 结构体 中的 st_size 可以获得
	pid_t l_pid; /*锁定动作的进程*/getpid()函数获得
};

//通常将 l_start 和 l_len 都设为 0, l_whence 为 SEEK_SET 表示整个文件。
参数 cmd,常用的如下:
加锁解锁:
置为 F_GETLK:获取文件锁定的状态。
置为 F_SETLK:设置文件锁定的状态。此时 flcok 结构体的 l_type 值必须是 F_RDLCK、F_WRLCK 或F_UNLCK。如果无法建立锁定,则返回-1对于 F_RDLCK:共享锁(或读锁),许多不同的进程可以拥有文件同一(或重叠)区域上的共享锁。只要任一进程拥有共享锁,那么就不能再有进程可以获得该区域上的独占锁。为了获得一把共享锁,文件必须为“读”或 “读/写”方式打开。对于 F_WRLCK:独占锁(或写锁),只有一个进程可以在文件的任一特定区域拥有独占锁。一旦一个进程拥有了这样的锁,其他任何进程都无法在该区域上获得任何类型的锁。为了获得一把独占锁,文件必须为“写”或“读 /写”方式打开。对于F_UNLCK:解锁,用来清除锁。

#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
	int fd = open("b.txt", O_RDWR);
	if(-1 == fd){
		perror("open error");
		exit(-1);
	}
	struct flock lock, locka;
	lock.l_type = F_RDLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	lock.l_pid = getpid();
	if(fcntl(fd, F_SETLK, &lock) == 0){
		if(lock.l_type == F_RDLCK){
			printf("read lock is set by %d\n", lock.l_pid);
		}else if(lock.l_type == F_WRLCK){
			printf("write lock is set by %d\n", lock.l_pid);
		} 
	}
	
	fcntl(fd, F_GETLK, &locka);
	if(locka.l_type == F_RDLCK){
		printf("read lock has been set by %d\n", locka.l_pid);
	}
	if(locka.l_type == F_WRLCK){
		printf("write lock has been set by %d\n", locka.l_pid);
	}

	sleep(10);
	close(fd);
	return 0;
	
}

文件锁有两种类型,读取锁(共享锁)和写入锁(互斥锁)。
对于已经加读取锁的文件,再加写入锁将会失败,但是允许其它进程继续加读取锁;
对于已经加写入锁的文件,再加读取锁和写入锁都将会失败。
注意:文件锁只会对其它试图加锁的进程有效,对直接访问文件的进程无效。还是可以读写文件。文件锁并不能
阻止对文件有读写权限的任何其他进程对文件进行的读写操作。加上相应的锁只是告诉别人我此时在访问文件,
起一个标识的作用,至于应用,一个应用程序的多个进程在都需要修改一个数据文件时,这种锁就显的重要了。
多个进程修改的文件区不同时就可以并行执行,有交差时就可以互斥执行。另外,在对加了锁的文件进行读写操
作时,应采用底层的 read 和 write 函数,因为高层的 fread 和 fwrite 函数会有缓冲区。这样就不能实现同步更新
了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值