最近在看底层系统相关开源代码的时候,发现自己linux下面的基础编程真的没有过关,重新捧起《linux程序设计,第三版》好好学习一下基础编程,文件编程当然是第一站咯,今天看完,好好总结一下吧。
小条例
一切皆文件:在linux操作系统中,文件为操作系统服务和设备提供了一个简单而统一的接口,即一切皆文件。应用程序可以像使用文件一样使用磁盘文件,串行口,打印机和其他设备。目录也是一种特殊文件。
系统调用和标准库:在一个进程中,对文件进行访问可以包括两部分。第一部分通过调用“系统调用”或者设备驱动程序接口等相关函数,切换到内核空间,进行访问文件,该方法相对于函数调用来说,需要在用户代码和内核代码切换,效率底下(减小开销的方法:让每次系统调用完成尽可能多的功能,比如每次读取大量数据而不是一个字符)。第二部分是在用户空间里,调用标准库函数里面的函数来访问文件,标准库函数是在系统调用的基础上,提供了缓存功能,减少系统调用的次数,经而提供程序的效率。
文件描述符:在进程中,通过文件描述符来关联已经打开的文件,文件描述符是一个小值整数,通过文件描述符,可以访问已经打开的文件或者设备。在程序启动时候,默认打开3个描述符:0:标准输入,1:标准输出,2:标准错误。在标准库中通过FILE *结构体来封装文件描述,对应默认的三个描述符:stdin,stdout,stderr。
文件操作头文件
#include<stdio.h>//标准库头文件
#include<dirent.h>//目录相关操作函数
#include<unistd.h>//文件系统调用相关函数
#include<sys/stat.h>//文件属性相关标志位的宏定义
#include<sys/types.h>//linux标准类型定义和宏定义
#include<errno.h>//错误代码
文件stat属性结构
文件stat结构:该结构中包含了文件属性信息,可以通过下面函数进行得到
#include <unistd.h>stat和lstat以文件目录为参数,如果文件是符号链接时,lstat返回的是符号链接信息,而stat返回原始文件的信息。其他情况下面,两个函数返回结果一样,下面主要分析一下stat结构,首先看stat结构体:
#inlcude <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes,struct stat *buff);
int stat(const char *path,struct stat *buff);
int lstat(const char *path,struct stat *buff);
struct stat其中mode_t是一个比较重要的内容,它存储了文件类型和文件权限相关信息。下面对其进行一个简单分析
{
dev_t st_dev; /* 文件所在设备的ID*/
ino_t st_ino; /* iinode节点信息*/
mode_t st_mode; /* 文件权限和文件类型信息*/
nlink_t st_nlink; /* 链向此文件的连接数(硬连接)*/
uid_t st_uid; /* user id*/
gid_t st_gid; /* group 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; /* 文件的权限,属组或内容最后一次修改的时间- */
};
mode_t是32位的整形变量类型,各位代表的信息如下。
S_IFMT 0170000 利用该掩码来检查文件类型可以通过位算法来得到具体信息比如:(mode & S_IRWXU)==S_IXUSR来判断是否有执行的权限。另外可以通过下面宏函数进行相应的检查,返回boolean类型:
S_IFSOCK 0140000 socket文件
S_IFLNK 0120000 符号链接文件
S_IFREG 0100000 正常文件
S_IFBLK 0060000 特殊的块设备文件
S_IFDIR 0040000 目录
S_IFCHR 0020000 特殊的字符设备文件
S_IFIFO 0010000 管道文件
S_ISUID 0004000 UID位
S_ISGID 0002000 group-ID位
S_ISVTX 0001000 sticky位
S_IRWXU 00700 利用该掩码来检查属主权限
S_IRUSR 00400 属主有读权限
S_IWUSR 00200 属主有读权限
S_IXUSR 00100 属主有写权限
S_IRWXG 00070 利用该掩码来检查属组权限
S_IRGRP 00040 属组有读权限
S_IWGRP 00020 属组有读权限
S_IXGRP 00010 属组有读权限
S_IRWXO 00007 利用该掩码来检查其他用户权限
S_IROTH 00004 其他用户有读权限
S_IWOTH 00002 其他用户有读权限
S_IXOTH 00001 其他用户有读权限
S_ISREG(m) 是否为正常文件
S_ISDIR(m) 是否为目录
S_ISCHR(m) 是否为字符设备
S_ISBLK(m) 是否为块设备
S_ISFIFO(m) 是否为管道文件
S_ISLNK(m) 是否为符号链接文件
S_ISSOCK(m) 是否为socket文件
文件访问模式
可以通过系统调用和标准库两种方式打开文件,两种打开i方式分别对应了一些访问模式
系统调用:
O_RDONLY:只读标准库:
O_WRONLY:只写
O_RDWR:读写
O_APPEND:附加在文档后面
O_ASYNC:启动IO的信号变量,
O_CLOEXEC:
O_CREAT:文件创建
O_DIRECT (Since Linux 2.4.10):直接IO,减小缓存对文件内容的影响。但是会影响系统性能。
O_DIRECTORY:不建议使用
O_EXCL :与O_CREAT一起,确定open操作为原子操作。保证文件创建成功,如果两个程序同时创建文件,并且文件已经存在,那么返回错误
O_LARGEFILE
O_NOATIME
O_NONBLOCK:非阻塞的方式打开
O_NOFOLLOW
O_TRUNC将文件长度设为零,丢弃现有文件
r或者rb:只读
w或者wb:只写
a或者ab:追加模式
r+或者rb+或者r+b,以修改的方式打开
w+或者wb+或者w+b,以修改的方式打开,并且设置文件长度为0
a+或者ab+或者a+b,一修改的方式打开,并且内容追家在文件尾部
其中b为二进制文件,否则为字符文件
文件锁
在多个进程在操作同一个文件时,防止操作互相覆盖,需要采用文件锁的机制来解决文件的操作的冲突。linux下面提供了两种锁:锁文件和文件段锁。下面分别介绍一下两种锁锁文件
多个进程操作同一个文件最直观的方法是:对每一个需要锁的文件创建一个全局的互斥“元素”,只有一个进程“获得”该元素,才可以操作文件,操作结束以后必须释放该元素。本节,我们把该元素叫着“锁文件”。linux的系统调用open函数可以通过创建一个系统唯一的文件来充当锁文件,该操作必须是原子操作,操作完以后,删除该锁文件。实例程序如下:#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int file_desc;
file_desc=open("file.in.lck",O_RDWR|O_CREAT|O_EXCL,S_IWUSR|S_IWGRP|S_IWOTH);
if(file_desc==-1){
printf("wrong\n");
return 0;
}
//锁文件创建好了,下面就是临界区
write(file_desc,"lck",3);
close(file_desc);
unlink("file.in.lck");
//临界区结束
return 1;
}
利用锁文件一般都用来控制诸如串行口之类的资源的独占式方法。但是不适合访问大型的共享文件。如果多个进程只需要操作文件一部分,采用锁文件效率底下。因此才有了文件段锁机制的存在。
文件段锁定
文件段锁定或者文件区锁定指的是通过文件中的锁定区域,实现文件部分锁定。linux提供两种方式fcntl系统调用和lockf调用。lockf调用是fcntl的替代接口,但是由于两者在底层实现机制不同,不允许混合使用两种类型的调用。
#include <unistd.h>
int lockf(int fd, int cmd, off_t len);
其中cmd参数有:
//F_LOCK 设置锁,并且阻塞等待。
//F_TLOCK:设置锁,但是立即返回,如果加锁失败,返回错误
//F_ULOCK解锁指定文件的一部分。
//F_TEST 锁测试
#include <unistd.h>
#include <fcntl.h>
nt fcntl(int fd, int cmd, struct flock *flock_structure);
其中cmd参数有:
F_GETLK:获得文件描述符fd打开的文件的锁信息。它不会尝试的去锁定文件。该函数通过设置flock结构来表明它可能需要的锁类型并定义感兴趣区域。如果调用成功,返回一个非-1值,如果文件已被锁定,应用程序会读取有关信息,并覆盖flock结构,调用应用程序可以检查flock中的l_pid来检查flock是否修改过来判断锁的状态。
F_SETLK:对fd的某个区域进行加锁或解锁。如果成功执行返回-1,该函数是立即返回。
F_SETLKW:和F_SETLK一样,但是会一直等待执行结果。
程序结束以后会自动清楚各种锁
flock结构:
struct flock
{
short int l_type;
short int l_whence;
#ifndef __USE_FILE_OFFSET64
__off_t l_start;
__off_t l_len;
#else
__off64_t l_start;
__off64_t l_len;
#endif
__pid_t l_pid;
};
结构体中l_type 是文件锁定的类型有F_RDLCK共享性读锁定,F_WRLCK独占性写锁定和F_UNLCK释放锁定
成员 l_whence 和lseek类似,有几个可选的参数 SEEK_SET 文件头 SEEK_CUR当前位置SEEK_END文件末尾该字段设定锁定的区域的启始地址
区域的长度 l_len 也就是锁定的长度若该值为0则表示锁定的区域从其起点开始(由l_start和l_whence决定)开始直至最大可能位置为址!该值不能在文件的起始位置之前开始或越过该超始位置。为了锁定整个文件,通常的做法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0
相关函数
系统调用
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);
int close(int fd);
flags和mode参数见上方相关描述。对于两个进程同时打开了一个文件,会出现操作彼此覆盖问题,解决该问题的方法是文件锁。
ssize_t write(int fd, const void *buf, size_t count);两个函数都返回的是实际读写的大小,如果返回-1,表示出现了错误。
ssize_t read(int fd, void *buf, size_t count);
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
详细见上面
off_t lseek(int fd, off_t offset, int whence);设置读写指针, 返回从文件头到当前设置处的偏移值,如果为-1表示出现错误。设置的方法可以相对当前位置,相对结束位置,绝对位置。分别对于whence(SEEK_CUR,SEEK_END,SEEK_SET)。
int dup(int oldfd);文件描述符的复制功能。在管道通信里面,该功能比较好
int dup2(int oldfd, int newfd);
int chmod(const char *path, mode_t mode);可以改变文件或者目录的访问权限。
int fchmod(int fd, mode_t mode);
int chmod(const char *path, mode_t mode);改变用户ID和组ID。
int fchmod(int fd, mode_t mode);
int symlink(const char *oldpath, const char *newpath);symlink和link分别创建符号链接和硬链接。unlink删除一个链接,当一个文件的连接数为0,才标识删除相应文件。
int link(const char *oldpath, const char *newpath);
int unlink(const char *pathname);
【硬连接】
硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。
【软连接】
另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于Windows的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。
int mkdir(const char *pathname, mode_t mode);创建和删除目录
int rmdir(const char *pathname);
int chdir(const char *path);得到或者改变当前工作目录。
int fchdir(int fd);
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);
标准函数库
stdio.hFILE *fopen(const char *path, const char *mode);
int fclose(FILE *fp);
int remove(const char *pathname);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fflush(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
int fgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
char *gets(char *s);
int ungetc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
int puts(const char *s);
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
dirent.h
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
void seekdir(DIR *dirp, long offset);
long telldir(DIR *dirp);
系统调用和标准库之间的转换
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
每一个系统调用的底层描述符和标准库的FILE对象之间都是相关联的。fileno如果失败,返回-1
错误处理
在系统调用和标准库的处理时候,都有各种原因导致错误,失败时,通过设置外部变量errno的值来指明失败的原因。因此程序必须在函数报错之后立即检查errno变量,防止错误被覆盖。man errno可以查到具体的错误代码,其中下面属于比较常见的错误:
EPERM 操作不允许
ENOENT 文件或者目录不存在
EINTR 系统调用被中断
EIO IO错误
EBUSY 设备或资源忙
EEX_IST 文件存在
EINVA_L 无效参数
EMFILE 打开文件过多
ENODEV 设备不存在
EISDIR 是一个目录
ENOTDIR 不是一个目录
上面的错误只是一个代码,可以通过下面的函数使其在说明之间转换
#include <string.h>其中strerror可以在实现转换。perror把error变量中报告的当前错误映射为一个字符串,并把它输出到标准错误输出。输入格式(s:[空格] 具体错误内容)
#include <errno.h>
extern int errno;
char *strerror(int errnum);
void perror(const char *s);
深入阅读
柳大的Linux讲义·基础篇(1)磁盘与文件系统 http://blog.csdn.net/poechant/article/details/7214673
柳大的Linux讲义·基础篇(2)Linux文件系统的inode http://blog.csdn.net/poechant/article/details/7214926
柳大的Linux讲义·基础篇(3)权限、链接与权限管理 http://blog.csdn.net/poechant/article/details/7215038