Linux系统编程------文件编程
一.知识点梳理
1. 文件IO
1.1 文件IO基础概念
文件读写是计算机系统中非常重要的一个功能,读写过程是一个程序、操作系统、硬盘紧密协作的过程,涉及程序、缓存、文件系统、驱动、硬盘控制器、硬盘等多个方面
1.2 文件IO操作函数
1.2.1 open函数
- int open(const char *pathname, int oflag,…/**,mode_t mode */)
- 参数解释
pathname:打开或者创建的文件的名称
oflag:函数的多个选择项
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读写打开
O_APPEND:每次写数据时都添加到文件尾端(原子操作)
O_TRUNC 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。
O_NOCTTY 如果pathname指的是终端设备,则不将此设备分配作为此进程的控制终端
O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式
O_CREAT:若文件不存在,则创建它,不存在则需要使用第三个参数,给创建的文件添加权限
例如:int fd = open(“test.txt”, O_CREAT, 0666);注意有个nmask为0002,可能会导致创建出来的权限为4
- 函数的作用
打开或者新建一个文件
- 函数返回值
成功返回文件描述符,失败返回-1
- 文件描述符
对于内核而言,所有打开的文件都由文件描述符引用。文件描述符是一个非负整数
在POSIX-1中,幻数0、1、2被代换成符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,分别表示标准输入、标准输出和标准错误
1.2.2 creat函数
- int creat(const char *pathname, mode_t mode) ;
- 函数参数含义:
pathname:打创建的文件的名称
mode_t mode:打开文件的方式
缺点:只能已只读的方式打开文件 - 函数作用
创建一个只读的文件
- 返回值
若成功为只写打开的文件描述符,若出错为- 1
1.2.3 close函数
- int close (int filedes);
- 函数参数含义:
filedes:文件描述符 - 函数作用
关闭文件,同时也释放该进程加在该文件上的所有记录锁。
- 返回值:
成功则返回0,失败则返回-1
1.2.4 lseek函数
- off_t lseek(int filedes, off_t offset, int whence) ;
- 函数参数含义:
filedes:文件描述符
off_t offset:文件偏移量与whence有关
whence:文件起始的位置
SEEK_ SET:将该文件的位移量设置为距文件开始处offset 个字节。
SEEK_CUR:将该文件的位移量设置为其当前值加offset, offset可为正或负。
SEEK_END:则将该文件的位移量设置为文件长度加offset, offset可为正或负。
- 函数的作用
便宜到指定的位置,并且按照指定的偏移量进行读取数据
- 返回值
若lseek成功执行,则返回新的文件位移量,为此可以用下列方式确定一个打开文件的当前位移量
1.2.5 read函数
- ssize_t read(int filedes, void *buff, size_t nbytes) ;
- 函数参数含义:
filedes:文件描述符
buff:读取数据存放的地址
nbytes:读取数据的大小,特别注意,nbytes的大小应该小于buff的存储大小 - 返回值
实际读到的字节数,若已到文件尾为0,若出错为- 1
1.2.6 write函数
- ssize_t write(int filedes, const void * buff, size_t nbytes) ;
- 函数参数含义:
filedes:文件描述符
buff:要写到文件内的内容
nbytes:一次写进去的大小 - 函数的作用
向文件内写内容
- 返回值
若成功为已写的字节数,若出错为- 1
1.2.7 fcntl函数
- int fcntl(int filedes, int cmd,…/* int arg * / ) ;
- 函数参数含义:
filedes:文件描述符
cmd:要执行的命令
1.复制一个现存的字符串:cmd = F_DUPFD,文件描述符作为函数值返回。
2.获得或设置文件描述符标记:cmd = F_GETFD或F_SETFD
3.获得或设置文件状态标志:cmd = F_GETFL 或 F_SETFL
4.获得或设置记录锁:cmd = F_GETLK、F_SETLK、SETLKW
nbytes:一次写进去的大小
- 返回值
若成功则依赖于cmd(见下),若出错为- 1
1.2.8 dup和dup2函数
- int dup(int filedes) ;
- int dup2(int filedes, int filedes2) ;
- 函数参数含义:
filedes:文件描述符
filedes2:指定的文件描述符的数值 - 函数的作用:
dup:返回的新文件描述符一定是当前可用文件描述符中的最小数值
dup2:则可以用filedes2参数指定新描述符的数值
- 返回值
若成功为新的文件描述符,若出错为- 1
举个例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd = open("a.txt", O_RDONLY);
printf("old fd_values : %d\n", fd);
fd = dup2(fd, 24);
printf("new fd_values : %d\n", fd);
close(fd);
return 0;
}
1.2.9 ioctl函数
- int ioctl(int filedes, int cmd, . . . ) ;
- 函数参数含义:
filedes:文件描述符
cmd:指定的文件描述符的数值 - 函数的作用:
ioctl 函数是I/O操作的杂物箱。它能执行非常多的我们其它I/O操作不能完成的操作,最常见的便是对终端I/0的控制或对硬件设备文件控制。
- 返回值
若成功为新的文件描述符,若出错为- 1
1.3 文件与目录操作
1.3.1 stat函数
-
int stat(const char *pathname, struct stat *buf) ;
-
int fstat(int filedes,struct stat *bu f) ;
-
int lstat(const char *pathname, struct stat *buf) ;
-
函数参数含义:
pathname:文件路径和名称
buf:结构体指针 -
函数的作用:
stat函数返回一个与此命名文件有关的信息结构s t a t函数返回一个与此命名文件有关的信息结构
- struct stat结构体:
struct stat
{
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小)
unsigned long st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
- 文件的类型和存取的权限st_mode;
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符装置文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
- 返回值
若成功则为0,若出错则为-1
1.3.2 access函数
- int access(const char *pathname, int mode) ;
- 函数参数含义:
pathname:文件路径和名称
mode:指定的文件描述符的数值
R_OK 测试读许可权
W_OK 测试写许可权
X_OK 测试执行许可权
F_OK 测试文件是否存在
- 函数的作用:
是对文件的进行一些检验,检验是否有读权、写权、执行权、是否存在
- 返回值
若成功为新的文件描述符,若出错为- 1
1.3.3 文件和目录操作
1.3.3.1 打开目录
-
DIR *opendir(const char *name);
-
头文件:#include <dirent.h>
-
函数参数含义:
pathname:目录名称 -
函数的作用:
打开一个目录
- 返回值
返回出一个DIR指针
1.3.3.2 读目录
struct dirent *readdir(DIR *dirp);
//每次只返回一个文件或目录的信息,当readdir返回NULL的时候表示目录里面已经没有文件或目录可读了。
struct dirent
{
long d_ino; /* inode number */
off_t d_off; /* offset to this dirent */
unsigned short d_reclen; /* length of this d_name */
char d_name [NAME_MAX+1]; /* filename (null-terminated) */
}
1.3.3.3 关闭目录
- int closedir(DIR *dirp);
- 头文件:#include <dirent.h>
- 函数参数含义:
dirp:一个结构指针
1.3.3.4 举个列子:输出一个目录下所有的文件的名称(一层)
#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, char const *argv[])
{
DIR *dir = opendir("/usr/");
struct dirent *p;
while ((p = readdir(dir)) != NULL)
{
if (strcmp(p->d_name, ".") == 0 || strcmp(p->d_name, "..") == 0)
{
continue;
}
printf("%s ", p->d_name);
}
printf("\n");
closedir(dir);
return 0;
}
1.4 文件IO记录锁
1.4.1 记录锁概念
- 记录锁是干什么的?
记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。
1.4.2 记录锁的操作
- 通过使用fcntl函数来申请给文件上锁:
fcntl的cmd是F_GETLK、F_SETLK或F_SETLKW(wait)。第三个参数是一个指向flock结构的指针。
F_SETLK尝试上锁,不阻塞,如果其它进程已经锁住了,无法上锁时,直接返回失败
如果没有被锁住的话,直接返回上锁成功
F_SETLKW,会阻塞,如果其它进程已经锁住了,无法上锁时,fcntl函数会等待其它进程释放锁为止才返回
- 通过一个struct flock结构体对文件进行锁的操作:
- struct flock结构体的定义如下:
struct flock {
short l_type; /* 记录锁类型,F_RDLCK,F_WRLCK或F_UNLCK */
off_t l_start; /* 起始位置偏移量,单位字节,是相对于l_whence而言 */
short l_whence; /* SEEK_SET,SEEK_CUR,或SEEK_END */
off_t l_len; /* 长度,单位字节,值为0时表示一直到文件尾 */
pid_t l_pid; /* 使用F_GETLK时返回此值 */
}
- 锁的类型
F_RDLCK:共享读锁
F_WRLCK:独占性写锁
F_UNLCK:解锁
- 各种状态下加锁的规则
- 举个列子
//创建一个read_lck.c文件将文件设置为读锁
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fd = open("a.txt", O_RDONLY); //只读打开一个txt文件
struct flock lck; //锁的结构体
lck.l_type = F_RDLCK;
lck.l_start = 0;
lck.l_whence = SEEK_SET;
lck.l_len = 0;
printf("开始上读锁!\n");
fcntl(fd, F_SETLKW, &lck); //设置读锁
printf("读锁上锁成功!\n");
getchar();
lck.l_type = F_UNLCK; //接触读锁
printf("读锁解锁成功!\n");
}
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
创建一个write_lck.c文件将文件设置为写锁
int fd = open("a.txt", O_WRONLY);
struct flock lck;
lck.l_type = F_WRLCK;
lck.l_start = 0;
lck.l_whence = SEEK_SET;
lck.l_len = 0;
printf("开始上写锁!\n");
fcntl(fd, F_SETLKW, &lck);
printf("写锁上锁成功!\n");
getchar();
lck.l_type = F_UNLCK;
printf("写锁解锁成功!\n");
}
- 程序运行
- 1.首先对文件上读锁,上锁成功
- 2.在对文件上读锁,依旧可以上锁成功
- 3.对文件上写锁,处于阻塞中
- 4.解除文件的一个读锁,依旧没发上写锁
- 5.解除所有读锁,写锁上锁成功
- 6.上读锁,无法上读锁
- 7.解除写锁,读锁上锁成功
1.5 原子操作
- 基础概念
原子操作指的是由多步组成的操作,如果该操作原子地执行,则或者执行完所有步,或者一步也不执行,不可能只执行所有步的一个子集;
通俗的讲,就是做一件事情,要么不做,要么就一口气做完,不能做到一半去干其它事情。
二. 重难点总结
1. 标准IO和文件IO的区别在哪里?使用的时候需要注意什么?
1.最大的一个区别莫过于,标准IO是C语言的库函数,使用时会先将数据放到缓存区里面,当遇到以下几种情况下才会输出
(1).遇到换行
(2).遇到IO口输出,比如putchar()等等
(3).程序正常结束
然而文件IO不存在缓存区,是直接通过内核进程调配的。
2.操作的设备上来区分,文件I/O主要针对文件操作,读写硬盘等,它操作的是文件描述符,标准I/O针对的是控制台,打印输出到屏幕等,它操作的是字符流
- 举个简单的例子
int main()
{
printf("Hello World!");
while(1);
}
//执行程序,不会输出Hello World!
int main()
{
printf("Hello World!\n");
while(1);
}
//执行程序会输出Hello world!
2. 文件共享是怎么进行的?如果两个独立的进程对同一个文件进行操作,是怎样做的?
I/O的数据结构
(1)每个进程再记录表中都有一个记录项,记录项中包含一张打开文件描述符表
(2)内核为所有打开文件维持一张文件表
(3)每个打开文件都有一个v结点结构
如果两个独立进程各自打开了同一文件,则有图所示的安排。我们假定第一个进程使该文件在文件描述符3上打开,而另一个进程则使此文件在文件描述符4上打开。打开此文件的每一个进程都得到一个文件对象,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件对象的一个理由:这种安排使每个进程都有它自己对该文件的当前位移量。这种情况不会增加对应的打开文件引用计数,而会增加dentry的引用
3.什么是死锁?
死锁就是一个进程已经对文件1上了写锁,此时还想对另一个文件2上写锁,但是文件2上也有一个写锁,给文件2上锁的进程此时也想给文件2上写锁,这个时候就产生了矛盾;
进程1等着进程2解开写锁之后上锁,而进程2还等着进程1解开写锁后上锁,陷入了死循环,所以就形成的死锁。
三. 总结反思
这一节对文件编程进行了学习,学习了IO口的10个函数、以及目录操作的6个函数,基本掌握了函数的使用方法,能在相应的场合使用,在学校中,也遇到了很多细节上的问题,尤其是在标准IO的缓存区这里,混淆了很多,但是经过与文件IO的比较之后,解决了迷惑,明白了缓存区的一些误区;
文件编程的难度总体不是很大,但是需要熟练地去使用和记忆