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的比较之后,解决了迷惑,明白了缓存区的一些误区;
文件编程的难度总体不是很大,但是需要熟练地去使用和记忆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值