系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,会有用户空间到内核空间切换的开销。但通过系统调用来访问文件是最直接的方式。系统调用函数直接作用于操作系统内核的设备驱动程序从而实现文件访问。文件描述符fd是一个非负整型值,每新打开一个文件,所获得的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:0-standard input,1-standard output,2-standard error。
系统调用open的作用是打开一个文件,并返回这个文件的描述符。
1,如果两个程序同时打开一个文件,会得到两个不同的文件描述符。
如果同时对两个文件进行操作,他们各自操作,互不影响,彼此相互覆盖(后写入的覆盖先写入的)。为了防止文件按读写冲突,可以使用文件锁的功能。【因为文件描述符是进程的私有属性】
2,新文件描述符总是使用未用描述符的最小值。
参见:http://blog.csdn.net/gexiaobaohelloworld/article/details/16106073#t11
例如如果一个程序关闭了自己的标准输出(这里标准输出完全等于1,也就是说描述符1一定是用来做输出的,只不过默认绑定到屏幕设备),然后再次调用open,文件描述符1就会被重新使用,并且标准输出(还是1,标准输出就是1)将被有效重定向到另一个文件或设备(现在绑定的)
3,运行中的程序能够一次打开的文件数目是有限制的。
这个限制由头文件limits.h中的OPEN_MAX常数定义,它随着系统地不同而不同,但POSIX规范要求它至少要为16。
头文件: #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> 函数原型: int open(const char *path, int oflags); int open(const char *path, int oflags, mode_t mode ); 参数说明: path:准备打开的文件或设备名字。 oflags:指出要打开文件的访问模式。 O_RDONLY 【3选1】以只读方式打开 O_WRONLY 【3选1】以只写方式打开 O_RDWR 【3选1】以读写方式打开 O_APPEDN 【|可选】以追加方式打开 O_TRUNC 【|可选】把文件长度设为零,丢弃以后的内容。 O_CREAT 【|可选】如果需要,就按参数mode中给出的访问模式创建文件。 O_EXCL 【|可选】与O_CREAT一起调用,确保调用者创建出文件。使用这个模式可防止两个程序同时创建一个文件,如果文件已经存在,open调用将失败。 mode:当使用O_CREAT标志的open来创建文件时,我们必须使用三个参数格式的open调用。第三个参数mode 是几个标志按位OR后得到的。 S_IRUSR:读权限,文件属主 S_IWUSR:写权限,文件属主 S_IXUSR:执行权限,文件属主 S_IRGRP:读权限,文件所属组 S_IWGRP:写权限,文件所属组 S_IXGRP:执行权限,文件所属组 S_IROTH:读权限,其他用户 S_IWOTH:写权限,其他用户 S_IXOTH:执行权限,其他用户
close系统调用用于“关闭”一个文件。文件描述符被释放,并能够重新使用。
当一个进程终止时,内核自动关闭它所有打开的文件。很多程序都利用了这一功能而不显式地用close关闭文件。
头文件: #include <unistd.h> 函数原型: int close(int fd); 参数说明: int:函数返回值close成功返回0,出错返回-1
头文件: #include <unistd.h> 函数原型 size_t read(int fd, void *buf, size_t nbytes); 参数说明: fd:文件描述符,标识要读取的文件。如果为0,则从标准输入读数据。 buf:缓冲区,用来存储读入的数据。 nbytes:要读取的字符数。 size_t:返回值,返回成功读取的字符数,它可能会小于请求的字节数。-1表示出现错误。
头文件: #include <unistd.h> 函数原型: size_t write(int fd, const void *buf, size_t nbytes); 参数说明: fd:文件描述符,目标文件。例如:fd的值为1,就向标准输出写数据, buf:待写入的字符串。 nbytes:要写入的字符数。 size_t:函数返回值,返回成功写入文件的字符数。-1表示出现错误。
为了向用户提供一个统一的接口,设备驱动程序封装了所有与硬件相关的特性。硬件的特有功能可通过ioctl完成。ioctl提供了一个用于控制设备及其描述符行为和配置底层服务的接口。终端、文件描述符、甚至磁带机都可以又为他们定义的ioctl。
头文件: #include <unistd.h> 函数原型: int ioctl(int fd, int cmd,,,,,,);
头文件: #include <unistd.h> #include <sys/types.h> 函数原型: off_t lseek(int filedes, off_t offset, int whence); 参数说明: off_set:参数用来指定位置 whence:参数定义该偏移值的用法。 whence可以取下列值 SEEK_SET:offset是一个绝对位置 SEEK_CUR:offset是相对于当前位置的一个相对位置 SEEK_END:offset是相对于文件尾的一个相对位置 off_t:lseek返回从文件到文件指针被设置处的字节偏移,失败时返回-1.参数offset的类型off_t是一个与具体实现有关的整数类型,它定义在sys/types.h中
fstat系统调用返回与打开的文件描述符相关的文件的状态信息,该信息将会写到buf结构中,buf的地址以参数形式传递给fstat。
头文件: #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> 函数原型: int fstat(int fildes, struct stat *buf); int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf);相关的函数stat和lstat返回的是通过文件名查到的状态信息。它们的结果基本一致,但当文件是一个符号链接时,lstat返回的是该符号链接本身的信息,而stat返回的是该链接指向的文件的信息。stat结构的成员在不同的UNIX系统上会有所变化,但一般会包括表3-4中所示的内容。
stat成员 说 明 st_mode 文件权限和文件类型信息 st_ino 与该文件关联的inode st_dev 保存文件的设备 st_uid 文件属主的UID号 st_gid 文件属主的GID号 st_atime 文件上一次被访问的时间 st_ctime 文件的权限、属主、组或内容上一次被改变的时间 st_mtime 文件的内容上一次被修改的时间 st_nlink 该文件上硬连接的个数例如,如果想测试一个文件“代表的不是一个目录,设置了属主的执行权限,不再有其他权限”,我们可以使用如下的代码进行测试:
struct stat statbuf; mode_t modes; stat(“filename”,&statbuf); modes = statbuf.st_mode; if(!S_ISDIR(modes) && (modes & S_IRWXU) == S_IXUSR)
stat结构中返回的st_mode标志还有一些与之关联的宏,它们定义在头文件sys/stat.h中。这些宏包括对访问权限、文件类型标志以及一些用于帮助测试特定类型和权限的掩码的定义。
访问权限标志与前面的open系统调用中的内容是一样的。
文件类型标志包括: S_IFBLK:文件是一个特殊的块设备。 S_IFDIR:文件是一个目录。 S_IFCHR:文件是一个特殊的字符设备。 S_IFIFO:文件是一个FIFO设备(命名管道) S_IFREG:文件是一个普通文件。 S_FLNK:文件是一个符号链接。 其他模式标志包括: S_ISUID:文件设置了SUID位。 S_ISGID:文件设置了SGID位。 用于解释st_mode标志的掩码包括: S_IFMT:文件类型。 S_IRWXU:属主的读/写/执行权限。 S_IRWXG:属组的读/写/执行权限。 S_IRWXO:其他用户的读/写/执行权限。还有一些用来帮助确定文件类型的宏定义。它们只是对经过掩码处理的模式标志和相应的设备类型标志进行比较。它们包括:
S_ISBLK:测试是否是特殊的块设备文件。 S_ISCHR:测试是否是特殊的字符设备文件。 S_ISDIR:测试是否是目录。 S_ISFIFO:测试是否是FIFO设备。 S_ISREG:测试是否是普通文件。 S_ISLNK:测试是否是符号链接。
dup,dup2系统调用。dup提供了复制文件描述符的方法,使我们能够通过两个或者更多个不同的文件描述符来访问同一个文件。这可以用于在文件的不同位置对数据进行读写。
头文件: #include <unistd.h> dup系统调用复制文件描述符fildes,返回一个新的文件描述符。 int dup(int fildes); dup2系统调用则是通过明确指定描述符来把一个文件描述符复制为另一个文件描述符 int dup2(int fildes,int fildes2);
#include <stdio.h>
读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。
函数原型: FILE *fopen(const char *filename, const char *mode); 参数说明: filename:打开文件的文件名 mode:打开的方式 "r"或"rb":以只读方式打开 "w"或"wb":以只写方式打开 "a"或"ab":以读方式打开,添加到文件的结尾处 "r+"或"rb+"或"r+b":打开更新(读和写) "w+"或"wb+"或"w+b":打开更新,将其长度变为零 "a+"或"ab+"或"a+b":打开更新,添加到文件结尾处 [注意]b表明这个文件是二进制文件而不是文本文件. FILE:返回值,成功是返回一个非空的FILE *指针。失败返回NULL
函数原型: size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream); 参数说明: ptr: 要读取数据的缓冲区,也就是要存放读取数据的地方。 size:指定每个数据记录的长度。 nitems: 计数,给出要传输的记录个数。 size_t:返回值,成功读取到数据缓冲区的记录个数,当到达文件尾时,他的返回值可能会消耗与nitems,甚至可以是0
函数原型: size_t fwrite(const coid *ptr, size_t size , size_t nitimes, FILE *stream);
实际上,由于库函数对文件的操作最终是通过系统调用实现的,因此,每打开一个文件所获得的FILE结构指针都有一个内核空间的文件描述符fd与之对应。同样有相应的预定义的FILE指针:stdin-standard input,stdout-standard output,stderr-standard error。
函数原型: int fclose(FILE *stream);
fflush函数的作用是把文件流中所有未写出的数据全部写出。 处于效率考虑,在使用库函数的时候会使用数据缓冲区,当缓冲区满的时候才进行写操作。使用fflush函数可以将缓冲区的数据全部写出,而不关心缓冲区是否满。fclose的执行隐含调用了fflush函数,所以不必再fclose执行之前调用fflush。
函数原型: int fflush(FILE *stream);
函数原型: int fseek(FILE *stream, long int offset, int whence); 参数说明: off_set:参数用来指定位置 whence:参数定义该偏移值的用法。 whence可以取下列值 SEEK_SET:offset是一个绝对位置 SEEK_CUR:offset是相对于当前位置的一个相对位置 SEEK_END:offset是相对于文件尾的一个相对位置 int:函数返回值。0表示成功,-1表示失败。
函数原型: int fgetc ( FILE * stream ); 函数说明: 函数实现,从流stream中读一个字符。可以将标准输入stdin作为它的实参,这时候从标准输入读取一个字符。fgetc函数读字符时遇到文件结束符,函数返回一个文件结束标记EOF。 函数原型: int getc(FILE * stream); 函数说明: 宏实现,宏实现版的fgetc 函数原型: int getchar ( void ); 函数说明: 宏实现,实现为#define getchar() fgetc(stdin)。 说明:getc、getchar都是通过宏定义借助fgetc实现。
函数原型: char * fgets (char * str, int num, FILE *stream); 函数说明: 从流stream中读入最多num个字符到字符数组str中,当遇到换行符时、或读到num-1个字符时停止。自动加上'\0'空字符结尾 函数原型: char * gets ( char * str ); 函数说明: 从标准输入stdin读取一个字符串,遇到换行或结束时候终止。不同于fgets,他没有指定num,所以需要注意字符数组str的大小。
函数原型: int fputc ( int character, FILE * stream ); 函数说明: 把一个字符写到输出流。失败则返回EOF。 函数原型: int putc ( int character, FILE * stream ); 函数说明: 宏实现,通过宏定义和fputc实现 函数原型: int putchar ( int character ); 函数说明: 宏实现,#define putchar(c) fputc(c, stdout)
int fputs ( const char * str, FILE * stream ); int puts ( const char * str ); 说明:两者之间无宏定义实现关系。puts(const char *str)近似等效于fputs(cosnt char *str, stdout),不同点是前者还输出一个'\n'
int printf(const char *format[,argument,...]); printf函数把输出送到[标准输出]。 int fprintf(FILE *stream,const char *format[,argument,...]) fprintf函数把输出送到[指定文件流]。 int sprintf( char *buffer, const char *format [, argument] ... ); sprintf函数把输出和一个结尾空字符写到作为参数传递过来的[字符串s]里。这个字符串必 须足够大以容纳所有的输出数据。
%d:%i:以十进制格式输出一个整数。 %o:%x:以八进制或十六进制格式输出一个整数。 %c:输出一个字符。 %s:输出一个字符串。 %f:输出一个(单精度)浮点数。 %e:以科学计数法格式输出一个双精度浮点数。 %g:以一般格式输出一个双精度浮点数。
int scanf(const char *format, ...); scanf函数从[标准输入]读入到format中。 int fscanf(FILE *stream, const char *format, ...); fscanf函数从[文件流(FILE *stream)]读入到format中。 int sscanf(const char *str, const char *format, ...); sscanf函数从[字符串str]读入到format中。
设置缓冲区大小,位置。本函数应该在打开流后,立即调用,在任何对该流做输入输出前使用。
函数原型: int setvbuf(FILE *stream, char *buf, int type, unsigned size); 参数说明: stream :指向流的指针 ; buf : 期望缓冲区的地址; type : 期望缓冲区的类型: _IOFBF(满缓冲):当缓冲区为空时,从流读入数据。或者当缓冲区满时,向流写入数 据。 _IOLBF(行缓冲):每次从流中读入一行数据或向流中写入一行数据。 _IONBF(无缓冲):直接从流中读入数据或直接向流中写入数据,而没有缓冲区。 size : 缓冲区内字节的数量。
函数原型: int ferror(FILE *stream);ferror 函数测试一个文件流的错误标识。如果该标识被设置就返回一个非零值,否则返回零。
函数原型: int feof(FILE *stream);
feof 函数测试一个文件流的文件尾标识。如果该标识被设置就返回一个非零值,否则返回零。
函数原型: void clearerr(FILE *stream);clearerr 函数的作用是清除由 stream 指向的文件流的文件尾标识和错误标识。
库函数调用 | 系统调用 |
#include <unistd.h> | #include <stdlib.h> |
移植性好,在所有的ANSI C编译器版本中,C库函数是相同的,系统无关 | 移植性差,各个操作系统的系统调用是不同的 |
调用函数库中的一段程序(或函数)【本质通过系统调用实现】 | 调用系统内核的服务 |
与用户程序相联系 | 是操作系统的一个入口点 |
在用户地址空间执行 | 在内核地址空间执行 |
它的运行时间属于“用户时间” | 它的运行时间属于“系统”时间 |
属于过程调用,调用开销较小 | 需要在用户空间和内核上下文环境间切换,开销较大 |
在C函数库libc中有大约300个函数 | 在UNIX中大约有90个系统调用 |
使用文件描述符fd,实质为一个非负整型 Linux系统默认分配了3个文件描述符值: 0:标准输入 1:标准输出 2:标准错误输出 |
使用流(stream)概念,它被实现为指向结构FILE的指针。 在启动程序时,有三个文件流是自动打开的。他们是: stdin: 标准输入 stdout: 标准输出 stderr: 标准错误输出 |
read和fread
其实fread和read的区别是,fread并不一定每次者会调用read。
我认为fopen和open最主要的区别是fopen在用户态下就有了缓存,在进行read和write的时候减少了用户态和内核态的切换,而open则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列快;如果随机访问文件open要比fopen快。
函数原型: int fileno(FILE *stream);fileno 函数确定文件流使用的文件描述符。
函数原型: FILE *fdopen(int fildes, const char *mode);
fdopen 在一个已打开的文件描述符上创建一个新的文件流,实际上,这个函数的作用是为一个已经打开的文件描述符提供 sdtio 缓冲区。
#include <unistd.h> #include <stdio.h> #include <dirent.h> #include <sys/stat.h> #include <stdlib.h> #include <string.h> void printdir(char * dir,int depth){ DIR * dp; struct dirent * entry; struct stat statbuf; if((dp = opendir(dir))==NULL){ fprintf(stderr,"cannot open directory:%s\n",dir); return; } chdir(dir); while((entry = readdir(dp))!=NULL){ lstat(entry->d_name,&statbuf); if(S_ISDIR(statbuf.st_mode)) if(strcmp(".",entry->d_name)==0|| strcmp("..",entry->d_name)==0) continue; else{ printf("%*s%s/\n",depth,"",entry->d_name); printdir(entry->d_name,depth+4); } else printf("%*s%s\n",depth,"",entry->d_name); } chdir(".."); closedir(dp); } int main(){ printdir("/home/",0); return; }
头文件 #include <sys/stat.h> 函数原型: int chmod(const char *path, mode_t mode); 函数说明: chmod()会依参数mode权限来更改参数path指定文件的权限.
头文件 #include <unistd.h> 函数原型: int chown(const char *path, uid_t owner, gid_t group);
头文件: #include <unistd.h> 函数原型: int unlink(const char * pathname ); int link(const char *oldpath, const char *newpath); int symlink(const char *oldpath, const char *newpath); 函数说明: link()以参数newpath指定的名称来建立一个新的连接(硬连接)到参数oldpath所指定的已存在文件. 如果参数newpath指定的名称为一已存在的文件则不会建立连接. 成功则返回0, 失败返回-1, 错误原因存于errno.linux命令rm实际调用unlink
头文件 #include <sys/stat.h> 函数原型: int mkdir (const char *filename, mode_t mode) 函数说明: 返回0表示成功,返回-1表述出错。使用该函数需要包含头文件sys/stat.h
头文件 #include <unistd.h> 函数原型: int rmdir(const char *pathname); 函数说明: rmdir函数用以删除指定目录
头文件 #include <unistd.h> 函数原型: int chdir(const char *path); 函数说明: chdir()用来将当前的工作目录该变成为参数path所指的目录. 若成功返回0, 失败返回-1, errno为错误代码.
头文件 #include <unistd.h> 函数原型: char *getcwd(char *buf, size_t size); 函数说明: getcwd()会将当前的工作目录绝对路径复制到参数buf所指的内存空间, 参数size为buf的空间大小. 在调用此函数时, buf所指的内存空间要足够大, 若工作目录绝对路径的字符串长度超过参数size大小, 则回值NULL, errno的值则为ERANGE. 倘若参数buf为NULL, getcwd()会依参数size的大小自动配置内存(使用malloc()), 如果参数size也为0, 则getcwd()会依工作目录绝对路径的字符串程度来决定所配置的内存大小, 进程可以在使用完此字符串后利用free()来释放此空间. 执行成功则将结果复制到参数buf所指的内存空间, 或是返回自动配置的字符串指针. 失败返回NULL, 错误代码存于errno.
opendir()函数打开一个目录名所对应的目录流。并返回指向目录流的指针。流定位在目录流的第一个条目。(其实和Open()一样,只不过Open打开的是文件流,流定位于文件头) 如果打开流失败,则返回空。如果成功,则返回类似句柄的DIR指证。
头文件 #include <sys/types.h> #include <dirent.h> 函数原型: DIR *opendir(const char *name); 函数说明: opendir()用来打开参数name指定的目录, 并返回DIR*形态的目录流, 和open()类似, 接下来对目录的读取和搜索都要使用此返回值. 成功则返回DIR*型态的目录流, 打开失败则返回NULL.
头文件 #include <sys/types.h> #include <dirent.h> 函数原型: struct dirent *readdir(DIR *dir); 函数说明: readdir()返回参数dir目录流的下个目录进入点. 结构dirent定义如下: struct dirent{ ino_t d_ino;//此目录进入点的inode ff_t d_off;//目录文件开头至此目录进入点的位移 signed short int d_reclen;//d_name的长度, 不包含NULL字符 unsigned char d_type;//d_name所指的文件类型 char d_name[256];//文件名 }; 成功则返回下个目录进入点, 有错误发生或读取到目录文件尾则返回NULL. EBADF参数dir为无效的目录流.后续的readdir调用将返回后续的目录项。
头文件 #include <sys/types.h> #include <dirent.h> 函数原型: off_t telldir(DIR *dir); 函数说明: telldir()返回参数dir目录流目前的读取位置. 此返回值代表距离目录文件开头的偏移量, 有错误发生时返回-1. EBADF表示参数dir为无效的目录流.
头文件 #include <sys/types.h> #include <dirent.h> 函数原型: void seekdir(DIR *dir, off_t offset); 函数说明: seekdir()用来设置参数dir目录流目前的读取位置, 在调用readdir()时便从此新位置开始读取. 参数offset代表距离目录文件开头的偏移量. EBADF表示参数dir为无效路径.
头文件 #include <sys/types.h> #include <dirent.h> 函数原型: int closedir(DIR *dir); 函数说明: closedir()关闭参数dir所指的目录流. 关闭成功则返回0, 失败返回-1, 错误原因存于errno中. EBADF参数dir为无效的目录流.
#include <errno.h> //errno
经常在调用linux 系统api 的时候会出现一些错误,比方说使用open() write() creat()之类的函数有些时候会返回-1,也就是调用失败,这个时候往往需要知道失败的原因。在程序代码中包含 #include <errno.h>,然后每次程序调用失败的时候,系统会自动用用错误代码填充errno这个全局变量,这样你只需要读errno这个全局变量就可以获得失败原因了。
示例程序:
#include <stdio.h> #include <string.h> #include <errno.h> int main(int argc, char* argv[]) { FILE *fp; if ((fp = fopen(argv[1], "r")) == NULL) { perror("perror"); printf("strerror:%s\n", strerror(errno)); } exit(0); }
输出:
perror: No such file or directory strerror:Illegal seek
void perror(const char *s) perror是错误输出函数,在标准输出设备上输出一个错误信息。参数s一般是参数错误的函数
char *strerror(int errnum); 通过参数errnum(也就是errno),返回错误信息