在Linux中,大多数的资源都可以以文件的方式进行处理,常见的文件类型有:
l 普通文件
l 无名管道和命名管道
l 目录
l 设备
l 符号连接
l 套接字
普通文件:也称磁盘文件,并且能够进行随机的数据存储(能够自由seek定位到某一个位置);
管道:是一个从一端发送数据,另一端接收数据的数据通道;
目录:也称为目录文件,它包含了保存在目录中文件列表的简单文件。
设备:也称为特殊文件,该类型的文件提供了大多数物理设备的接口。它又分为两种类型:
字符型设备和块设备。字符型设备一次只能读出和写入一个字节的数据,包括调制解调器、中端、打印机、声卡以及鼠标;块设备必须以一定大小的块来读出或者写入数据,块设备包括CD-ROM、RAM驱动器和磁盘驱动器等,一般而言,字符设备用于传输数据,块设备用于存储数据。
符号链接:类似于Windows的快捷方式和Linux里的别名,指包含到达另一个文件路径的文件。
套接字:在Linux中,套接字也可以当作文件来进行处理。
基于文件指针的文件操作函数是ANSI标准函数库的一部分。
原型为:
#include <stdio.h>
FILE *fopen(const char *pach,const char *mode);
FILE *fdopen(int fd,const char *mode);
int fclose(FILE *stream);
fopen以mode的方式打开或创建文件,如果成功,将返回一个文件指针,失败则返回NULL.
fopen创建的文件的访问权限将以0666与当前的umask结合来确定。
mode的可选模式列表
模式
|
读
|
写
|
位置
|
截断原内容
|
创建
|
rb
|
Y
|
N
|
文件头
|
N
|
N
|
r+b
|
Y
|
Y
|
文件头
|
N
|
N
|
wb
|
N
|
Y
|
文件头
|
Y
|
Y
|
w+b
|
Y
|
Y
|
文件头
|
Y
|
Y
|
ab
|
N
|
Y
|
文件尾
|
N
|
Y
|
a+b
|
Y
|
Y
|
文件尾
|
N
|
Y
|
在Linux系统中,mode里面的’b’(二进制)可以去掉,但是为了保持与其他系统的兼容性,建议不要去掉。
ab和a+b为追加模式,在此两种模式下,无论文件读写点定位到何处,在写数据时都将是在文件末尾添加,所以比较适合于多进程写同一个文件的情况下保证数据的完整性。
fdopen根据已经打开的文件描述符打开一文件指针,对同一个文件既打开文件描述符又打开文件指针,将容易出现问题,但是在多进程程序中,往往需要传递文件描述符,所以此类混合文件操作必须掌握。
基于文件指针的数据读写函数较多,可分为如下几组:
数据块读写 :
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread从文件流stream 中读取nmemb个元素,写到ptr指向的内存中,每个元素的大小为size个字节
fwrite从ptr指向的内存中读取nmemb个元素,写到文件中,每个元素size个字节。
所有的文件读写函数都从文件的当前读写点开始读写,读写完以后,当前读写点自动往后移动size*nmemb个字节。
格式化读写:
#include <stdio.h>
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, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
其中printf相当于fprintf(stdout,format,...);
以f和vf开头的将格式化后的字符串写入到文件流stream中,
以s和vs开头的将格式化后的字符串写入到字符串str中.
以v开头的函数往往用在可变参数个数的函数内部调用,例如:
int MyPrintf(const char *format,...)
{
va_list lstArg;
FILE *pStream=fopen(“a.txt”,”ab”);
va_start(lstArg,pszFormat);
vfprintf(pStream,pszFormat,lstArg);
va_end(lstArg);
fclose(pStream);
}
字符读写:
使用下列函数可以一次读写一个字符
#include <stdio.h>
int fgetc(FILE *stream);
int fputc(int c, FILE *stream);
int getc(FILE *stream);
int putc(int c, FILE *stream);
int getchar(void);
int putchar(int c);
getchar和putchar从标准输入输出流中读写数据,其他函数从文件流stream中读写数据。
注意此组每次只会读写一个字节的数据,但是类型是以整型表示的。
行读写:
char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);
int puts(const char *s);
char *gets(char *s);
fgets和fputs从文件流stream中读写一行数据;puts和gets从标准输入输出流中读写一行数据。
fgets可以指定目标缓冲区的大小,所以相对于gets安全,但是fgets调用时,如果文件中当前行的字符个数大于size,则下一次fgets调用时,将继续读取该行剩下的字符,fgets读取一行字符时,保留行尾的换行符。
fputs不会在行尾自动添加换行符,但是puts会在标准输出流中自动添加一换行符。
文件定位:
文件定位指读取或设置文件当前读写点,所有的通过文件指针读写数据的函数,都是从文件的当前读写点读写数据的。
常用的函数有:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
fseek设置当前读写点到offset处,whence可以是SEEK_SET,SEEK_CUR,SEEK_END,这些值决定是从文件头、当前点和文件尾计算偏移量offset.
ftell获取当前的读写点
rewind将文件当前读写点移动到文件头0
1.3. 目录操作
获取、改变当前目录:
原型为:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
// 获取当前目录
int chdir(const char *path);
// 修改当前目录
int fchdir(int fd);
// 用文件描述符的方式修改当前目录
创建和删除目录:
原型为:
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int mkdir(const char *pathname, mode_t mode); //创建目录,mode是目录权限
int rmdir(const char *pathname);
// 删除目录
获取目录信息:
原型为:
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
// 打开一个目录
struct dirent *readdir(DIR *dir);
// 读取目录的一项信息,并返回该项信息的结构体指针
void rewinddir(DIR *dir);
// 重新定位到目录文件的头部
int closedir(DIR *dir);
// 关闭目录文件
读取目录信息的步骤为:
1、 用opendir函数打开目录;
2、 使用readdir函数迭代读取目录的内容,如果已经读取到目录末尾,又想重新开始读,则可以使用rewinddiw函数将文件指针重新定位到目录文件的起始位置;
3、 用closedir函数关闭目录
示例:
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc,char *argv[])
{
dirent *pDirInfo;
DIR *pDir;
//
if(argc<2)
pDir=opendir(".");
else
pDir=opendir(argv[1]);
if(pDir==NULL){
perror("open dir fail!");
return -1;
}
while(pDirInfo=readdir(pDir))
printf("%s/n",pDirInfo->d_name);
closedir(pDir);
return 0;
}
在进程一开始运行,就自动打开了三个对应 设备的文件,它们是标准输入、输出、错误流,分别用全局文件指针stdin、stdout、stderr表示,stdin具有可读属性,缺省情况下是指从 键盘的读取输入,stdout和stderr具有可写属性,缺省情况下是指向屏幕输入数据。
示例:
#include <stdio.h>
#include <unistd.h>
int main()
{
char szBuf[32];
printf("Input string:"); // 向屏幕输出一字符串
fgets(szBuf,sizeof(szBuf),stdin);// 从键盘读入一行字符串
fprintf(stdout,"The string is:%s",szBuf);// 向屏幕输出一行字符串
return 0;
}
内核为每个进程维护一个已打开文件的记录 表,文件描述符是一个较小的正整数,它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读、写、创建、删除等操 作。常用的基于文件描述符的函数有open、creat、close、read、write、ftruncate、lseek、fsync、fstat、 fchown、flock、fcntl、dup、dup2、select和ioctl。基于文件描述符的文件操作并非ANSI C的函数。
open和creat都能打开和创建函数,原型为
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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);
flags和mode都是一组掩码的合成值,flags表示打开或创建的方式,mode表示文件的访问权限。
flags的可选项有
掩码
|
含义
|
O_RDONLY
|
以只读的方式打开
|
O_WRONLY
|
以只写的方式打开
|
O_RDWR
|
以读写的方式打开
|
O_CREAT
|
如果文件不存在,则创建文件
|
O_EXCL
|
仅与O_CREAT连用,如果文件已存在,则强制open失败
|
O_TRUNC
|
如果文件存在,将文件的长度截至0
|
O_APPEND
|
已追加的方式打开文件,每次调用write时,文件指针自动先移到文件尾,用于多进程写同一个文件的情况。
|
O_NONBLOCK
|
非阻塞方式打开
|
O_NODELAY
|
非阻塞方式打开
|
O_SYNC
|
只有在数据被真正写入物理设备设备后才返回
|
int creat(const char *pathname,mode_t mode);
等价于
open(pathname,O_CREAT|O_TRUNC|O_WRONLY,mode);
文件使用完毕后,应该调用close关闭它,一旦调用close,则该进程对文件所加的锁全都被释放,并且使文件的打开引用计数减1,只有文件的打开引用计数变为0以后,文件才会被真正的关闭,文件引用计数主要用于多进程之间文件描述符的传递。
读写文件的函数原型为:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
函数lseek将文件指针设定到相对于whence,偏移值为offset的位置
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
whence 可以是下面三个常量的一个
SEEK_SET 从文件头开始计算
SEEK_CUR 从当前指针开始计算
SEEK_END从文件尾开始计算
2.5. 获取文件信息
可以通过fstat和stat函数获取文件信息,调用完毕后,文件信息被填充到
结构体struct stat buf中,原型为:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
结构体stat的定义为:
struct stat {
dev_t st_dev; /* 如果是设备,返回设备表述符,否则为0*/
ino_t st_ino; /* i 节点号 */
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; /* 文件大小,字节表示 */
blksize_t st_blksize; /* 块大小*/
blkcnt_t st_blocks; /* 块数 */
time_t st_atime; /* 最后访问实际*/
time_t st_mtime; /* 最后修改实际*/
time_t st_ctime; /* 创建时间 */
};
对于结构体的成员st_mod,有一组宏可以进行文件类型的判断
宏
|
描述
|
S_ISLNK(mode)
|
判断是否是符号链接
|
S_ISREG(mode)
|
判断是否是普通文件
|
S_ISDIR(mode)
|
判断是否是目录
|
S_ISCHR(mode)
|
判断是否是字符型设备
|
S_ISBLK(mode)
|
判断是否是块设备
|
S_ISFIFO(mode)
|
判断是否是命名管道
|
S_ISSOCK(mode)
|
判断是否是套接字
|
2.6. 文件的锁定
在多进程对同一个文件进行读写访问时,为了保证数据的完整性,有时需要对文件进行锁定。
可以通过fcntl对文件进行锁定和解锁。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock *lock);
参数cmd置为F_GETLK或F_SETLK可以获取或设置文件锁定信息。
参数struct flock为文件锁信息。
文件锁有两种类型,读取锁(共享锁)和写入锁(互斥锁)。对于已经加读取锁的文件,再加写入锁将会失败,但是允许其它进程继续加读取锁;对于已经加写入锁的文件,再加读取锁和写入锁都将会失败。
注意:文件锁只会对其它试图加锁的进程有效,对直接访问文件的进程无效。
示例:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
bool LockFile(int fd,int iType)
{
struct flock lock;
lock.l_whence=SEEK_SET; // 从文件头开始
lock.l_pid=getpid(); // 当前进程ID
lock.l_start=0; // 开始锁定点,
lock.l_len=0; // 结束锁定点,0表示整个文件锁定
lock.l_type=iType; // 锁定类型
if(fcntl(fd,F_SETLK,&lock)==0)
return true;
perror("lock fail");
return false;
}
int main(int argc,char *argv[])
{
int fd=open("./a.txt",O_CREAT|O_RDWR);
int iLock=0;
if(fd<0){
printf("errno:%d/n",errno);
perror("open fail");
return -1;
}
fchmod(fd,0666);
if(argc>1){
if(strstr(argv[1],"write")){
if(!LockFile(fd,F_WRLCK)){
return -3;
}
}else if(strstr(argv[1],"read")){
if(!LockFile(fd,F_RDLCK)){
return -3;
}
}
}
lseek(fd,0,SEEK_END);
if(write(fd,"abc",3)<0){
perror("write fail");
return -2;
}
sleep(10);
close(fd);
return 0;
}
2.7. 文件描述符的复制
函数dup和dup2可以实现文件描述符的复制。原型为:
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
文件描述符的复制是指用另外一个文件描述符指向同一个打开的文件,它完全不同于直接给文件描述符变量赋值,例如:
描述符变量的直接赋值:
char szBuf[32];
int fd=open(“./a.txt”,O_RDONLY);
int fd2=fd;
close(fd); // 导致文件立即关闭
printf(“read:%d/n”,read(fd2),szBuf,sizeof(szBuf)-1); // 读取失败
close(fd2); // 无意义了
在此情况下,两个文件描述符变量的值相同,指向同一个打开的文件,但是内核的文件打开引用计数还是为1,
所以close(fd)或者close(fd2)都会导致文件立即关闭掉。
描述符的复制:
char szBuf[32];
int fd=open(“./a.txt”,O_RDONLY);
int fd2=dup(fd);
close(fd); // 当前还不会导致文件被关闭,此时通过fd2照样可以访问文件
printf(“read:%d/n”,read(fd2),szBuf,sizeof(szBuf)-1);
close(fd2); // 内核的引用计数变为0,文件正式关闭
dup2(int fdold,int fdnew)也是进行描述符的复制,只不过采用此种复制,新的描述符由用户用参数fdnew显示指定,而不是象dup一样由内核帮你选定。对于dup2, 如果fdnew已经指向一个已经打开的文件,内核会首先关闭掉fdnew所指向的原来的文件。如果成功dup2的返回值于fdnew相同,否则为-1
思考下面程序的结果:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc,char *argv[])
{
char szBuf[32]={0};
int fda=open("./a.txt",O_RDONLY);
int fdb=open("./b.txt",O_RDONLY);
int fdbb=dup(fdb);
int fda2=dup2(fda,fdb);
printf("fda:%d fdb:%d fdbb:%d fda2:%d",
fda,fdb,fdbb,fda2);
read(fdb,szBuf,sizeof(szBuf)-1);
printf("result:%s/n",szBuf);
close(fda);
close(fdb);
close(fdbb);
close(fda2);
}
2.8. 标准输入输出文件描述符
与标准的输入输出流对应,在更底层的实现是用标准输入、标准输出、标准错误文件描述符表示的。它们分别用STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO三个宏表示,值分别是0、1、2三个整型数字。
示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
char szBuf[32],szBuf2[50];
printf("Input string:");
fflush(stdout); // 要刷新标准输出流,才可以立即在屏幕上显示”Input string”
int iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf));
szBuf[iRet]=0; //read 是以无类型指针方式读的数据,不会自动在缓冲区后加0结束标记。
sprintf(szBuf2,"The string is:%s",szBuf);
write(STDOUT_FILENO,szBuf2,strlen(szBuf2));
return 0;
}