1文件描述符
内核为每个进程维护一个已打开文件的记录表,文件描述符是一个较小的正整数(0—1023),它代表记录表的一项,通过文件描述符和一组基于文件描述符的文件操作函数,就可以实现对文件的读、写、创建、删除等操作。
常用基于文件描述符的函数有open(打开)、creat(创建)、close(关闭)、read(读取)、write(写入)、ftruncate(改变文件大小)、lseek(定位)、fsync(同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、dup(复制)、dup2、select和ioctl。
基于文件描述符的文件操作并非ANSI C的函数。
如果不清楚某个函数的具体实现形式,可以通过下面的方式查询
man 函数名 查看该函数的帮助。
如果要复制里面的内容,按Ctrl+Insert键,再粘贴的话用:Shift+Insert键。
2打开、创建和关闭文件
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); //文件名 权限 //现在已经不常用了
creat函数等价于èopen(pathname,O_CREAT|O_TRUNC|O_WRONLY,mode);
open()函数出错时返回-1,相关参数如下:
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 |
同步打开文件,只有在数据被真正写入物理设备设备后才返回 |
mode的可选项有:
S_IRWXU 00700 权限,代表该文件所有者具有可读、可写及可执行的权限。
S_IRUSR 或S_IREAD,00400权限,代表该文件所有者具有可读取的权限。
S_IWUSR 或S_IWRITE,00200 权限,代表该文件所有者具有可写入的权限。
S_IXUSR 或S_IEXEC,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 权限,代表其他用户具有可执行的权限。
但是通常采用直接赋数值的形式,如:
1 int fd = open(“1.txt”,O_WRONLY | O_CREAT,0755); //表示给755的权限 2 if(-1 == fd) 3 { 4 perror("open failed!\n"); 5 exit(-1); 6 }
注意:LINUX中基于文件描述符的open函数,对于一个不存在的文件,不能通过O_WRONLY的方式打开,必须加上O_CREAT选项。
close用于文件的关闭:
int close(int fd);//fd表示文件描述词,是先前由open或creat创建文件时的返回值。
文件使用完毕后,应该调用close关闭它,一旦调用close,则该进程对文件所加的锁全都被释放,并且使文件的打开引用计数减1,只有文件的打开引用计数变为0以后,文件才会被真正的关闭。
3 读写文件
读写文件的函数原型为:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);//文件描述词 缓冲区 长度
ssize_t write(int fd, const void *buf, size_t count);
对于read和write函数,出错返回-1,读取完了之后,返回0, 其他情况返回读写的个数。
Example:将dir.c中的内容复制到dir.txt中,其中dir.txt起初不存在。
4改变文件大小
函数原型:
#include <unistd.h>
int ftruncate(int fd, off_t length);
函数ftruncate会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。如果原来的文件大小比参数length大,则超过的部分会被删去。
返回值 执行成功则返回0,失败返回-1。
实例:
1 int main() 2 { 3 int fd = open("a.txt", O_WRONLY); 4 ftruncate(fd, 1000); 5 close(fd); 6 return 0; 7 }
5文件定位
函数lseek将文件指针设定到相对于whence,偏移值为offset的位置
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);//fd文件描述词
whence 可以是下面三个常量的一个
SEEK_SET 从文件头开始计算
SEEK_CUR 从当前指针开始计算
SEEK_END 从文件尾开始计算
利用该函数可以实现文件空洞(对一个新建的空文件,可以定位到偏移文件开头1024个字节的地方,在写入一个字符,则相当于给该文件分配了1025个字节的空间,形成文件空洞)通常用于多进程间通信的时候的共享内存。
1 int main() 2 { 3 int fd = open("c.txt", O_WRONLY | O_CREAT); 4 lseek(fd, 1024, SEEK_SET); 5 write(fd, "a", 1); 6 close(fd); 7 return 0; 8 }
6获取文件信息
可以通过fstat和stat函数获取文件信息,调用完毕后,文件信息被填充到结构体struct stat变量中,函数原型为:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf); //文件名 stat结构体指针
int fstat(int fd, struct stat *buf); //文件描述词 stat结构体指针
结构体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_mode,有一组宏可以进行文件类型的判断
宏 |
描述 |
S_ISLNK(mode) |
判断是否是符号链接 |
S_ISREG(mode) |
判断是否是普通文件 |
S_ISDIR(mode) |
判断是否是目录 |
S_ISCHR(mode) |
判断是否是字符型设备 |
S_ISBLK(mode) |
判断是否是块设备 |
S_ISFIFO(mode) |
判断是否是命名管道 |
S_ISSOCK(mode) |
判断是否是套接字 |
通常用于判断:if(S_ISDIR(st.st_mode)){}
Example:获得文件的大小
1 #include<sys/stat.h> 2 #include<unistd.h> 3 main() 4 { 5 struct stat buf; 6 stat (“/etc/passwd”,&buf); 7 printf(“/etc/passwd file size = %d \n”,buf.st_size);//st_size可以得到文件大小 8 }
如果用fstat函数实现,如下:
int fd = open (“/etc/passwd”,O_RDONLY); //先获得文件描述词
fstat(fd, &buf);
实例:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <errno.h> 4 #include <sys/stat.h> 5 #include <sys/types.h> 6 #include <unistd.h> 7 #include <fcntl.h> 8 #include <time.h> 9 int main() 10 { 11 int fd = open("/home/wangxiao/0926/a.txt", O_RDONLY); 12 if(fd == -1) 13 { 14 perror("open error"); 15 exit(-1); 16 } 17 struct stat buf; 18 int iRet = fstat(fd, &buf); 19 if(iRet == -1) 20 { 21 perror("fstat error"); 22 exit(-1); 23 } 24 if(S_ISREG(buf.st_mode)) 25 { 26 printf("regular file!\n"); 27 } 28 if(S_ISDIR(buf.st_mode)) 29 { 30 printf("directory!\n"); 31 } 32 if(S_ISLNK(buf.st_mode)) 33 { 34 printf("link file!\n"); 35 } 36 printf("the size of file is : %d\n", buf.st_size); 37 time_t tt = buf.st_atime; 38 struct tm *pT = gmtime(&tt); 39 printf("%4d-%02d-%02d %02d:%02d:%02d\n", (1900+pT->tm_year), (1+pT->tm_mon), pT->tm_mday, (8+pT->tm_hour), pT->tm_min, pT->tm_sec); 40 // printf("the last access time is : %d\n", buf.st_atime); 41 close(fd); 42 return 0; 43 }
7 文件描述符的复制
系统调用函数dup和dup2可以实现文件描述符的复制,经常用来重定向进程的stdin(0),stdout(1),stderr(2)。
dup返回新的文件描述符(没有使用的文件描述符的最小编号)。这个新的描述符是旧文件描述符的拷贝。这意味着两个描述符共享同一个数据结构。
dup2允许调用者用一个有效描述符(oldfd)和目标描述符(newfd),函数成功返回时,目标描述符将变成旧描述符的复制品,此时两个文件描述符现在都指向同一个文件,并且是函数第一个参数(也就是oldfd)指向的文件。
原型为:
#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; //类似于C语言的指针赋值,当释放掉一个得时候,另一个已经不能操作了
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); //内核的文件打开引用计算+1,变成2了
close(fd); //当前还不会导致文件被关闭,此时通过fd2照样可以访问文件
printf(“read:%d\n”,read(fd2),szBuf,sizeof(szBuf)-1);
close(fd2); //内核的引用计数变为0,文件正式关闭
Example:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 int main(int argc,char *argv[]) 8 { 9 char szBuf[32]={0}; 10 int fda=open("./a.txt",O_RDWR); //假设a.txt的内容为:hello world 11 int fdaa=dup(fda); 12 read(fda,szBuf,4); 13 puts(szBuf); //关闭之前先输入原来的内容 14 close(fda); 15 // lseek(fdaa, 0, SEEK_SET); 16 read(fdaa,szBuf,sizeof(szBuf)); 17 puts(szBuf); //输出现在的内容 18 close(fdaa); 19 }
解析:假设a.txt中的内容为:hello world.上面的例子会发现第一次输出的结果是hell。关闭close(fda)的时候,文件实际上还没有真正的关闭,此时文件指针已经向后移动了。执行第二次read命令将o world读出来,最后关闭fdaa。
dup有时会用在一些特定的场合,如下:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <fcntl.h> 4 #include <sys/stat.h> 5 #include <sys/types.h> 6 #include <stdlib.h> 7 int main() 8 { 9 int fd = open("a.txt", O_WRONLY | O_CREAT); 10 if(fd == -1) 11 { 12 perror("open error"); 13 exit(-1); 14 } 15 printf("\n"); /* 必不可少 */ 16 close(1); 17 int fd2 = dup(fd); 18 close(fd); 19 printf("hello world\n"); 20 close(fd2); 21 return 0; 22 }
该程序首先打开了一个文件,返回一个文件描述符,因为默认的就打开了0,1,2表示标准输入,标准输出,标准错误输出。而用close(1);则表示关闭标准输出,此时这个文件描述符就空着了。后面又用dup,此时dup(fd);则会复制一个文件描述符到当前未打开的最小描述符,此时这个描述符为1.后面关闭fd自身,然后在用标准输出的时候,发现标准输出重定向到你指定的文件了。那么printf所输出的内容也就直接输出到文件了。
dup2(int fdold,int fdnew)也是进行描述符的复制,只不过采用此种复制,新的描述符由用户用参数fdnew显示指定,而不是象dup一样由内核帮你选定(内核选定的是随机的)。对于dup2,如果fdnew已经指向一个已经打开的文件,内核会首先关闭掉fdnew所指向的原来的文件。此时再针对于fdnew文件描述符操作的文件,则采用的是fdold的文件描述符。如果成功dup2的返回值于fdnew相同,否则为-1.
思考下面程序的结果:
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 int main(int argc,char *argv[]) 8 { 9 char szBuf[32]={0}; 10 int fda=open("./a.txt",O_RDONLY); 11 int fdb=open("./b.txt",O_RDONLY); 12 int fdbb=dup(fdb); 13 int fda2=dup2(fda,fdb); //可以设定为:int fda2 = dup2(fda,5);即自己设为5 14 printf("fda:%d fdb:%d fdbb:%d fda2:%d",fda,fdb,fdbb,fda2); 15 read(fdb,szBuf,sizeof(szBuf)-1); //此时fdb已经不再定位b.txt而是a.txt 16 printf("result:%s\n",szBuf); 17 close(fda); 18 close(fdb); 19 close(fdbb); 20 close(fda2); 21 }
8标准输入输出文件描述符
与标准的输入输出流对应,在更底层的实现是用标准输入、标准输出、标准错误文件描述符表示的。它们分别用STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO三个宏表示,值分别是0、1、2三个整型数字。
标准输入文件描述符 STDIN_FILENO 0
标准输出文件描述符 STDOUT_FILENO 1
标准错误输出文件描述符 STDERR_FILENO 2
示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 int main() 5 { 6 char szBuf[32],szBuf2[50]; 7 printf("Input string:"); 8 fflush(stdout); //要刷新标准输出流,才可以立即在屏幕上显示”Input string” 9 //fflush用于linux中的时候,只对fflush(stdout)有效。 10 int iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)); 11 szBuf[iRet]=0; //read是以无类型指针方式读的数据,不会自动在缓冲区后加0结束标记。 12 sprintf(szBuf2,"The string is:%s",szBuf); 13 write(STDOUT_FILENO,szBuf2,strlen(szBuf2)); 14 return 0; 15 }
9 I/O多路转接模型
I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,
而是让其中的一个函数等待,在这期间,I/O还能进行其他操作。如本节要介绍的select()函数,就是属于这种模型。
Select函数:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset,fd_set *writeset, fd_set *exceptionset,
const struct timeval * timeout);
返回:就绪描述字的正数目,0——超时,-1——出错
参数解释:
maxfd: 最大的文件描述符(其值应该为最大的文件描述符字 + 1)
readset: 内核读操作的描述符字集合
writeset:内核写操作的描述符字集合
exceptionset:内核异常操作的描述符字集合
timeout:等待描述符就绪需要多少时间。NULL代表永远等下去,一个固定值代表等待固定时间,0代表根本不等待,检查描述字之后立即返回。
其中readset、writeset、exceptionset都是fd_set集合。该集合的相关操作如下:
void FD_ZERO(fd_set *fdset); /* 将所有fd清零 */
void FD_SET(int fd, fd_set *fdset); /* 增加一个fd */
void FD_CLR(int fd, fd_set *fdset); /* 删除一个fd */
int FD_ISSET(int fd, fd_set *fdset); /* 判断一个fd是否有设置 */
一般来说,在使用select函数之前,首先要使用FD_ZERO和FD_SET来初始化文件描述符集,在使用select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关文件描述符之后,使用FD_CLR来清除描述符集。
另外,select函数中的timeout是一个struct timeval类型的指针,该结构体如下:
struct timeval
{
long tv_sec; /* second */ //秒
long tv_usec; /* microsecond */ //微秒
};
Example:多路转接模型 select
1 #include <sys/select.h> 2 #include <sys/time.h> 3 #include <sys/types.h> 4 #include <unistd.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #define FILENAME1 "a.txt" 10 #define FILENAME2 "dir.c" 11 int main() 12 { 13 char buf[10] = {0}; 14 int fd1 = open(FILENAME1,O_RDWR); 15 int fd2 = open(FILENAME2,O_RDWR); 16 int fd3 = open(FILENAME1,O_RDWR); 17 int fd4 = open(FILENAME2,O_RDWR); 18 if( (-1 == fd1) || (-1 == fd2) || (-1 == fd3) || (-1 == fd4) ) 19 { 20 perror("open"); 21 exit(-1); 22 } 23 fd_set fdrd,fdwr; //绑定读写集合 24 FD_ZERO(&fdrd); //清除以前读的绑定 25 FD_ZERO(&fdwr); //清除以前写的绑定 26 FD_SET(fd1,&fdrd); //将fd1与读绑定 27 FD_SET(fd2,&fdrd); 28 FD_SET(fd3,&fdwr); //将fd3与写绑定 29 FD_SET(fd4,&fdwr); 30 int max1 = fd1 > fd2 ? fd1 : fd2; //获取读绑定中的文件描述词最大值 31 int max2 = fd3 > fd4 ? fd3 : fd4; //获取写绑定中的文件描述词最大值 32 int max = max1 > max2 ? max1 : max2; //获得读写文件描述词最大值 33 struct timeval tv; //用于记录时间,表示过这么长时间不响应就退出 34 tv.tv_sec = 2; //秒 35 tv.tv_usec = 0; //微妙 36 while(1) 37 { 38 if( select(max+1, &fdrd, &fdwr, NULL, &tv) == -1 ) //从1—max+1查找 39 { 40 perror("select"); 41 break; 42 } 43 if( FD_ISSET(fd1,&fdrd) ) //如果fd1设置的是读绑定 44 { 45 read(fd1,buf,sizeof(buf)-1); 46 puts(buf); 47 sleep(1); 48 } 49 if( FD_ISSET(fd2,&fdrd) ) 50 { 51 read(fd2,buf,sizeof(buf)-1); 52 puts(buf); 53 sleep(1); 54 } 55 if( FD_ISSET(fd3,&fdwr) ) //如果fd3设置的是写绑定 56 { 57 write(fd3,buf,sizeof(buf)); 58 sleep(2); 59 } 60 if( FD_ISSET(fd4,&fdwr) ) 61 { 62 write(fd4,buf,sizeof(buf)); 63 sleep(2); 64 } 65 } 66 close(fd1); 67 close(fd2); 68 close(fd3); 69 close(fd4); 70 }
补充:
- 程序参数:
1 int main(int argc,char ** argv) 2 { 3 int i = 0; 4 for( ; i< argc; i++) 5 printf("%s",argv[i]); 6 }
参数argc表示命令行传入的参数个数,并且一次保存到argv数组中。例如程序叫main.c,最后执行的时候,如果直接是./main则argc=1,此时argv[0]就是./main;如果是./main aaa bbb,则argc=3,argv[0]:./main, argv[1]:aaa argv[2]:bbb.
- 日志
许多应用程序需要记录它们的活动,系统程序经常需要向控制台或日志文件写消息。这些消息可能指示错误、警告或者与系统状态有关的一般信息。通常是在/var/log目录下的messages中包含了系统信息。通过syslog可以向系统的日志发送日志信息。
函数原型如下:
#include <syslog.h>
void syslog(int priority, const char* message, arguments…);
对于priority有如下几个常见的:
LOG_EMERG 紧急情况
LOG_ALERT 高优先级故障(如:数据库崩溃)
LOG_CRIT 严重错误(如:硬件错误)
LOG_ERR 错误
LOG_WARNING 警告
LOG_NOTICE 需要注意的特殊情况
LOG_INFO 一般信息
LOG_DEBUG 调试信息(写不到messages里面)
1 #include <syslog.h> 2 main() 3 { 4 //openlog("log",LOG_PID|LOG_CONS|LOG_NOWAIT,LOG_USER); 5 syslog(LOG_ALERT,"this is alert\n"); 6 syslog(LOG_INFO,"this is info\n"); 7 syslog(LOG_DEBUG,"this is debug%d\t %s",10,"aaaa"); 8 syslog(LOG_ERR,"err"); 9 syslog(LOG_CRIT,"crit"); 10 //closelog(); 11 }
利用tail -10 /var/log/messages可以查看。
还可以通过函数openlog函数来改变日志信息的表达方式。openlog的原型如下:
#include <syslog.h>
void openlog(const char* ident, int logopt, int facility);
void closelog(void);
它可以设置一个字符串ident,该字符串会添加在日志信息的前面。你可以通过它来指明是哪个应用程序创建了这条信息。facility值为LOG_USER。logopt参数对后续syslog调用的行为进行配置。如下:
LOG_PID 在日志信息中包含进程标识符,这是系统分配给每个进程的一个唯一值
LOG_CONS 如果信息不能被记录到日志文件中,就把它们发送到控制台
- 日期
1 #include <time.h> 2 main() 3 { 4 char *wday[] = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday","saterday" }; 5 time_t timep; 6 struct tm *pTm; 7 time(&timep); 8 pTm = gmtime(&timep); 9 printf("%04d-%02d-%02d\t",(1900 + pTm->tm_year), (1 + pTm->tm_mon), (pTm->tm_mday)); 10 printf("%02d:%02d:%02d\t",(8 + pTm->tm_hour), (pTm->tm_min), (pTm->tm_sec)); //似乎小时总是相差8,所以加上8试试 11 printf("%s\n",wday[pTm->tm_wday]); 12 }