当前位置: 首页 > >

第三章 Linux下的文件编程??Linux C

发布时间:

1.概述

在Linux中普通文件和目录文件保存在称为物理设备的磁盘上。一套Linux系统支持若干物理盘,每个物理盘可定义一个或者多个文件系统。每个文件系统由逻辑块的序列组成,一个逻辑盘空间一般划分为几个用途各不相同的部分,即引导块、超级块、inode表和数据块。


①引导块:在文件系统的开头,通常为一个扇区,存放文件系统的引导程序,用于读入并启动操作系统;
②超级块:用于记录文件系统的管理信息。特定的文件系统定义了特定的超级块;
③inode表(索引节点):一个文件或目录占据一个索引节点,第一个索引节点是该文件系统的根节点,利用根节点。可以把一个文件系统挂在另一个文件系统的非叶节点上;
④数据块:用于存放文件数据或者管理数据。

Linux的文件系统


(1)超级块

用于描述一个文件系统的资源状态,如文件的大小、空闲空间位置信息,是针对银盘或者分区上的文件系统的整体信息。


(2)索引节点

索引节点是一个结构,它包含了一个文件的长度、访问及修改时间、权限、所属关系、磁盘中的位置等信息。文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元素对应。系统给每个索引节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号。linux文件系统将文件索引节点号和文件名同时保存在目录中。所以,目录只是将文件的名称和它的索引节点号结合在一起的一张表,目录中每一对文件名称和索引节点称为一个连接。对于一个文件来说有唯一的索引节点号与之对应,对于一个索引节点号,却可以有多个文件名与之对应。则在磁盘上的同一个文件可以通过不同的路径去访问它。
索引节点字段构成:


索引节点中并不包括文件名,文件名信息存放在目录文件中。在系统定义了stat结构(该结构定义于/usr/include/sys/stat.h文件中)来存放这些信息。
struct stat
{
dev_t st_dev; /*文件所在设备的ID,高字节为主设备号,低字节为从设备号*/
ino_t st_ino; /*文件的索引节点号*/
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; /*以字节计算的文件长度,对于设备文件此项为0*/
blksize_t st_blksize; /*文件系统I/O的块尺寸*/
blkcnt_t st_blocks; /*分配的块数*/
time_t st_atime; /*最*一次的访问时间*/
time_t st_mtime; /*最*一次修改时间*/
time_t st_ctime; /*最*一次状态改变时间*/
};

(3)文件类型
①普通文件

也就是我们通常操作计算机时所说的文件。比如文本,二进制文件。

②目录文件

目录在存储上跟文件一样,以byte为单位存放,不过内容却只是inode与文件名关系对(pair)。对目录的操作是不能像正常文件那样读写的,只能通过内核提供的系统调用来进行。
“.”和“..”这两个文件把文件结构中的上级目录与当前目录连接起来的。
目录是一个列表,记录Inode和文件名。

访问过程:


③链接文件

链接文件分成两种类型:硬链接文件和软(符号)链接文件。
硬链接就是文件名直接指到inode的文件指向方式。删除文件只是删除文件名与inode的link。跨文件系统的硬链接是不可能实现的。
符号链接,符号链接本身是一个正常的文件,它有自己的inode,在这个inode里记录了它要指向的文件的绝对路径。能跨文件系统的链接。符号链接的目标文件被删除后,相应的符号链接是不能被删除的。
对原文件的修改,软、硬链接文件内容也一样的修改,因为都是指向同一个文件内容的。

④设备文件


character device file:是字符设备文件,以指定字符(通常是1个字符)访问数据的。
block device file:是块设备文件,以固定长度的块访问数据的。

⑤管道文件


分为无名管道和有名管道。
一个进程将需要传递的数据或信息写入管道一端;另一进程则从管道的另一端取得所需的数据或信息。
采用先进先出的规则处理其中的数据。

2.文件描述符

对于Linux而言,所有对设备和文件的操作都可以使用文件描述符来进行,文件描述符就相当于文件的身份证号。
文件描述符是一个非负的整数,表示为int类型的对象,它是一个索引值,并指向内核中每个进程打开文件的记录表。
Linux中的每个进程可以有1024个文件描述符。
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符。
当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。


文件描述符0 (STDIN_FILENO) :标准输入文件,一般来说是键盘;
文件描述符1 (STDOUT_FILENO) :标准输出文件,一般输出到显示器;
文件描述符2 (STDERR_FILENO) :标准错误输出文件,一般也是输出到屏幕。
头文件

3.基本文件I/O(不带缓冲)操作
(1)open函数:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode); (创建新文件时使用)
pathname是要打开的文件名(包含路径名称,缺省时认为在当前路径下),
flags 可以取下面的一个值或者是几个值的组合:
标志(flags) 含义
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件。
O_CREAT 若欲打开的文件不存在则自动建立该文件。
O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。
此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的 资料也会消失。
O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
O_NDELAY 同O_NONBLOCK。
O_SYNC 以同步的方式打开文件。
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。  
说明:O_RDONLY、O_WRONLY、O_RDWR三个标志的取值分别为0、1、2,只能使用任意的一个,不能使用OR的方法联合使用。其它的标志可以用“|”符号链接。
如果使用了O_CREAT标志,就需要加入第三个参数,其组合如下:
符号 值(八进制) 含义
S_IRWXU 00700 文件属主有读、写、执行的权限
S_IRUSR(S_IREAD) 00400 文件属主有读权限
S_IWUSR(S_IWRITE) 00200 文件属主有写权限
S_XWUSR(S_IEXEC) 00100 文件属主有执行权限
S_IRWXG 00070 文件组成员有读、写、执行权限
S_IRGRP 00040 文件组成员有读权限
S_IXGRP 00010 文件组成员有执行权限
S_IRWXO 00007 其他用户有读、写、执行的权限
S_IROTH 00004 其他用户有读权限
S_IWOTH 00002 其他用户有写权限
S_IXOTH 00001 其他用户有执行权限
符号值说明:
除了可以通过上述宏进行“或”逻辑产生标志以外,我们也可以自己用数字来表示。
Linux总共用5个数字来表示文件的各种权限(从左至右):
第一位表示设置文件属主的ID;第二位表示设置文件组ID;第三位表示文件属主的权限位;第四位表示文件组成员的权限;
最后一位表示其他用户的权限。每个数字可以取 1(执行权限)、2(写权限)、4(读权限)、0(无)或者是这些值的和。
如果文件打开成功,open函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。
Open函数打开失败时返回-1。

(2)close函数:

int close(int fd);
fd是我们要关闭的文件描述符。
执行成功,返回值为0,否则,返回值为-1。

(3)read和write函数:

ssize_t read(int fd, const void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
参数fd为文件描述符,buf为输入/输出缓冲区的指针,count为缓冲区的大小 (以字节为单位)。
函数read()实现从文件描述符所指定的文件中读取count个字节到buf 所指向的内存缓冲区中,调用成功时,返回值为实际读取的字节数,错误时返回-1。
函数write()实现把count个字节从buf 指向的缓冲区中写到文件描述符所指向的文件中,调用成功时,返回值为实际写入的字节数,错误时返回-1。

(4)creat函数:

int creat(const char *filename, mode_t mode);
参数mode指定新建文件的存取权限,它同 umask 一起决定文件的最终权限(mode&umask),其中umask 代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用 umask()来改变:
int umask(int newmask);
该调用将umask 设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。
注:int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode); //等同于create函数。
open函数可以打开一个特殊设备的文件,而creat不能创建设备文件,创建特殊文件要用函数mknod来代替。

(5)lseek函数

off_t lseek(int fd, offset_t offset, int whence);
lseek()将文件读写指针相对whence移动 offset(可取负值)个字节。操作成功时,返回一个以字节为单位从文件头开始计算文件偏移量的值。
参数whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾
注:lseek(fd, -5, SEEK_CUR); //文件指针相对当前位置向前移动 5个字节
lseek(fd, 0, SEEK_END); //表示文件的长度

4.文件高级操作
(1)文本模式

低9位为文件的存取权限,分为属主、同组用户和其他用户3类,每类分为读、写和执行权限。
高7位:
符号 值(八进制值) 含义
S_IFMT 0170000 这些位决定文件类型
S_IFDIR 0040000 目录文件
S_IFCHR 0020000 字符设备文件
S_IFBLK 0060000 块设备文件
S_IFREG 0100000 普通文件
S_IFIFO 0001000 有名管道文件
S_ISUID 0400000 Set UID标志
S_ISGID 0200000 Set GID标志
S_IFLNK 0120000 符号链接
S_IFSOCK 0140000 套接字(Socket)
判断文件类型: struct stat buf;
stat("/home", &buf);
abc = buf.st_mode & S_IFMT; //与对应的标志位相与
if(abc == S_IFDIR) ... //结果与标志位比较

(2)确定和改变文件模式
①umask函数

文件创建时受到的“屏蔽”,也即遭到禁止的访问权限。
#include
#include
mode_t umask(mode_t cmask);
其中参数cmask是新设置的文件创建屏蔽码。
umask函数用cmask设置进程的当前文件屏蔽,然后返回umask原来的屏蔽值。

②chmod和fchmod函数

用于改变文件的所有权关系,即可以改变文件的属主ID和所属组ID。
#include
#include
int chmod(const char *filename,mode_t mode);
int fchmod(int fd,mode_t mode);
其中第一个参数filename是被设置的文件名(包括路径),第2个参数mode是文件模式。
调用成功时返回0,否则,返回-1。
chmod只对文件的inode节点进行操作,修改文件模式,而不修改文件本身。
fchmod它操作的是打开文件描述符给出的文件。

③rename函数

用来对文件重命名。
#include
int rename(const char*oldname,const char*newname);
其中的参数newname和oldname都是字符串指针。
调用成功返回0,失败返回-1。
rename的参数选项:
| newname所示文件不存在 | newname指向普通文件 | newname指向目录文件
????????????????????????????????????????????????????????????????????????????????????????????????
oldname指向普通文件 | 文件被重命名 | newname被删除,原 | 错误
| | 来名为oldname的文 |
| | 件被重命名为newname |
??????????????????????????????????????????????????????????????????????????????????????????????????
oldname指向目录文件 | 文件被重命名 | 错误 | newname所指向的目录
| | | 文件为空目录,则该
| | | 目录文件被删除,oldname
| | | 被重命名,否则出错

④truncate和ftruncate函数

修改文件的大小,截断文件长度。
#include
int truncate(char *pathname, size_t len);
int ftruncate( int fd, size_t len);
其中的参数len用于指定要将文件截取到的长度。两个函数分别针对文件路径和文件描述符。
调用成功时,返回值为0;调用失败时,返回值为-1。

⑤access函数

#include
int access(const char *pathname,int mode);
其中,pathname是希望检验的文件名(包含路径),mode是欲检查的访问权限。
access检查用户对一个文件的权限情况,根据mode的值检查调用进程对文件pathname是否具有读、写或执行的权限。
若进程实际用户具有mode所指出的权限,access返回0,否则返回-1。
access的访问权限(即mode值):
选项 说明
R_OK 检验调用进程是否有读访问权限
W_OK 检验调用进程是否有写访问权限
X_OK 检验调用进程是否有执行访问权限
F_OK 检验规定的文件是否存在

(3)查询文件信息

每一个文件都有3个时间戳与之相连:最*访问时间、最*修改时间,以及最*特性修改时间,
它们分别对应于stat结构的st_atime、st_mtime,以及st_ctime成员。
文件的访问时间st_atime给出最*一次读或者执行该文件的时间:修改时间st_mtime给出最*一次写该文件的时间,即文件内容被更改的时间;
特性修改时间st_ctime给出文件的inode被改变的时间,如改变文件的访问权限、文件的属主、文件的链接数等。

①utime和utimes函数

改变一个文件的访问时间和修改时间,但是没有函数可以改变文件的特性修改时间,因为inode是由系统来维护的。
#include
#include
int utime(const char *pathname,const struct utimbuf *times);
其中,参数pathname指明要更新时间的文件(包含路径),utimbuf是专用于utime函数的结构类型;参数times为该文件指定新的访问时间和修改时间,它可以是空指针。
utimbuf结构:
成员 说明
time_t actime 文件的访问时间
time_t modtime 文件的修改时间
如果times是空指针,文件的访问时间和修改时间均设置为当前时间。此时,要么进程的有效用户ID必须等于文件的用户ID,要么进程必须有该文件的写权限。
如果times不是空指针,它解释为指向utimbuf结构的指针并且用times的值更新文件的访问时间和修改时间。此时,进程的有效用户ID必须等于文件的用户ID,要么必须是超级进程。
utime调用成功返回0并且自动更新文件的特性修改时间;否则返回-1。

#include
int utimes(const char *pathname,const struct timeval values[2]);
utimes除了其第2参数与times不同之外,其余与utime完全相同。它的第2参数values是一个指向timeval结构的数组,此数组以微秒的精度指定文件pathname的访问时间和修改时间。
其中,values[0]指定文件的访问时间,values[1]指定文件的修改时间。
timeval结构
成员 说明
long tv_sec 自公元纪年以来的秒数
long tv_usec 秒后的微秒数
②stat、fstat和lstat函数
获取文件的其它有关状态信息。
#include
#include
#include
int stat(char*pathname, struct stat *buf);
int fstat(int fd, struct stat*buf);
int lstat(char*pathname, struct stat *buf);
其中,参数pathname为文件名,fd是文件描述符,buf为指向stat结构的指针。
stat函数将文件pahtname的信息存放在参数buf所指向的stat结构中。
lstat在指向符号链接文件时,返回的是符号链接本身的信息,而stat返回的是符号链接指向文件的信息。
fstat使用文件描述符指向文件。

(4)文件其他操作
①dup和dup2函数

复制一个现存的文件描述符。
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
其中,oldfd和newfd分别为复制前的文件描述符和复制后的文件描述符。
当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。
dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。  
dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
实际上,调用dup(oldfd)等效于,fcntl(oldfd, F_DUPFD, 0);
而调用dup2(oldfd, newfd)等效于,close(oldfd);fcntl(oldfd, F_DUPFD, newfd); 

②fcntl函数

用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性
#include
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
执行失败是返回-1;

参数cmd:


针对第2个参数,int cmd,fcntl函数有五种功能:
? 复制一个现存的描述符(cmd=F_DUPFD) 。
? 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD) 。
? 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL) 。
? 获得/设置异步I/O有权(cmd=F_GETOWN或F_SETOWN) 。
? 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)。
flock结构:
struct flock {
short l_type; /* 锁类型: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* l_start字段参照点: SEEK_SET(文件头), SEEK_CUR(文件当前位置), SEEK_END(文件尾) */
off_t l_start; /* 相对于l_whence字段的偏移量 */
off_t l_len; /* 需要锁定的长度 */
pid_t l_pid; /* 当前获得文件锁的进程标识(F_GETLK) */
};
关于加锁和解锁的说明有以下注意点:
(1)该区域可以在但钱文件尾端处开始或越过其尾端处开始,但是不能在文件起始位置之前开始或越过该起始位置。
(2)如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直到最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。
(3)为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为0,l_len说明为0。
每个进程可以在该字节区域上设置不同的读锁。
多进程时,给定的字节上只能设置一把写锁,并且写锁存在就不能再设其他任何锁,且该写锁只能被一个进程单独使用。
单个进程时,文件的一个区域上只能有一把锁,若该区域已经存在一个锁,再在该区域设置锁时,新锁会覆盖掉旧的锁,无论是写锁还时读锁。

(5)目录文件的操作
①getwd函数

函数getcwd和getwd返回进程的工作目录。
#include
char *getwd(char *pathbuf);
char *getcwd(char *pathbuf,size_t size);
函数getwd拷贝当前工作目录的绝对路径到字符串pathname中,并且要求工作目录的最大长度小于PATH_MAX。调用成功时,函数返回一个指向字符串的指针,否则返回NULL。
函数getcwd的作用与getwd相同,不同的是,它给出了存放路径名字符数组的大小。
注:
char *getcwd(char *buf, size_t size);
?参数*buf:保存当前目录的缓冲区
?参数size:在现代linux 中,buf 的长度至少可以为255 字节
?返回值:成功返回指向当前目录的指针,和buf 的值一样,错误返回NULL

char *getwd(char *buf);该函数已经过时,使用的时候会有警告
?参数*buf:保存当前目录的缓冲区
?返回值:成功返回指向当前目录的指针,和buf 的值一样,错误返回NULL

char *get_current_dir_name(void);
?参数:无
?返回值:成功返回指向当前目录的指针,错误返回NULL

②chdir和fchdir函数

函数chdir和fchdir重新制定文件的工作目录。
#include
int chdir(const char *pathname);
int fchdir(int fd);
其中,pathname是新的工作目录,该函数使得pathname指定的目录称为调用进程的当前工作目录,调用成功,返回值为0;调用失败返回-1。
fchdir函数与chdir作用相同,不同的是,新的工作目录由打开的未文件描述符fd决定。

③mkdir和rmdir函数

? mkdir函数用于创建目录。
#include
#include
#include
int mkdir(const char *path,mode_t mode);
其中,参数pathname是新创建目录的目录名,mode指定该目录的访问权限,这些位将受到文件创建方式屏蔽的修正。
若调用成功,mkdir将更新该目录的st_atime、 st_mtime 和st_ctime,同时更新其父目录的st_mtime 和st_ctime时间,然后返回0;否则返回-1。
? rmdir函数用于删除一个空目录。
#include
int rmdir(char *path);
使用rmdir函数时,目录必须为空,否则调用失败,返回-1。调用成功时,返回0。

④opendir函数

用于打开目录文件。
#include
#include
DIR *opendir(const char *dirname);
其中,参数pathname是要打开目录的路径名。函数返回值是DIR类型,是指向目录文件的结构指针。
调用成功时,返回值为一个目录指针;调用失败时,返回值为NULL。

⑤closedir函数

用于关闭已打开的目录文件。
#include
#include
int closedir(DIR *dirp);
其中,参数dp是要关闭的目录文件的指针,它是由opendir函数调用时获得的。
函数调用成功时,返回值为0;调用失败时,返回值为-1。

⑥readdir函数

读取目录文件内容。
#include
#include
struct dirent *readdir(DIR *dirp);
其中,参数fd是指向要访问目录文件的指针。
函数调用成功,返回值为指向dirent的结构指针,即返回下个目录进入点;调用失败,返回值为0。
dirent结构的定义:
struct dirent
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}

(6)特殊文件操作(略)

①mknod函数
②mount与umount函数
③链接与link函数
④符号链接与symlink和readlink函数



友情链接: