最近在看底层系统相关开源代码的时候,发现自己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结构:该结构中包含了文件属性信息,可以通过下面函数进行得到
#include <unistd.h> #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);stat和lstat以文件目录为参数,如果文件是符号链接时,lstat返回的是符号链接信息,而stat返回原始文件的信息。其他情况下面,两个函数返回结果一样,下面主要分析一下stat结构,首先看stat结构体:
struct stat { 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是一个比较重要的内容,它存储了文件类型和文件权限相关信息。下面对其进行一个简单分析
mode_t是32位的整形变量类型,各位代表的信息如下。
S_IFMT 0170000 利用该掩码来检查文件类型 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 其他用户有读权限可以通过位算法来得到具体信息比如:(mode & S_IRWXU)==S_IXUSR来判断是否有执行的权限。另外可以通过下面宏函数进行相应的检查,返回boolean类型:
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为二进制文件,否则为字符文件
#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); ssize_t read(int fd, void *buf, size_t count);两个函数都返回的是实际读写的大小,如果返回-1,表示出现了错误。
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); int fchmod(int fd, mode_t mode);改变用户ID和组ID。
int symlink(const char *oldpath, const char *newpath); int link(const char *oldpath, const char *newpath); int unlink(const char *pathname);symlink和link分别创建符号链接和硬链接。unlink删除一个链接,当一个文件的连接数为0,才标识删除相应文件。
【硬连接】
硬连接指通过索引节点来进行连接。在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);得到或者改变当前工作目录。
FILE *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, ...);
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> #include <errno.h> extern int errno; char *strerror(int errnum); void perror(const char *s);其中strerror可以在实现转换。perror把error变量中报告的当前错误映射为一个字符串,并把它输出到标准错误输出。输入格式(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