APUE——文件与文件IO
1 系统IO与文件IO的比较
文件IO的响应速度更快,但是标准IO的吞吐量更大!
系统IO比文件IO多了一步写入stdio buffer的操作,可以参考下图
注意标准IO和文件IO不要混用,因为两者的文件指针指向的buf不一样,前者是std buf,后者是内核buf(file*里有),可以用如下两个函数进行转换:
int fileno(FILE *stream); 将指针转成文件描述符
FILE *fdopen(int fd, const char *mode); 将文件描述符转成指针
2 文件操作
2.1 mode_t位图分析(16位)
关于mode_t类型,st_mode是一个16位的位图,用于表示文件类型,文件访问权限,以及特殊权限位
键入ls -l 查看文件状态,其中第一列为st_mode,其为16位的位图
实际16位如下所示,前四位表示文件类型,后九位表示文件权限
2.2 stat获取文件属性
int stat(const char *pathname, struct stat *statbuf);//返回statbuf
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
lstat与stat区别在于,lstat函数遇到link文件,不会
解析其源文件,只会解析其link文件本身,而stat会解析其源文件
struct stat结构体如下所示:
stat命令:
这里区分lstat与stat,如果入参为link类型,则stat描述的是符号链接链接的源文件,而lstat则描述符号链接文件
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char** argv)
{
int rs1,rs2;
struct stat *statbuf1= (struct stat *)malloc(sizeof(struct stat));
struct stat *statbuf2= (struct stat *)malloc(sizeof(struct stat));
rs1 = stat(argv[1],statbuf1);
rs2 = lstat(argv[1],statbuf2);
if(rs1<0)
{
perror("stat");
exit(1);
}
if(rs2<0)
{
perror("lstat");
exit(1);
}
printf("no1 of %d\n",statbuf1->st_blocks);
printf("no2 of %d\n",statbuf2->st_blocks);
free(statbuf1);
free(statbuf2);
exit(0);
}
2.3 umask
umask主要为了防止权限过松的文件
umask默认为0022,创建文件open,mkdir时,使用默认0666 & ~umask
也可以指定文件权限的位图,例如上例子open第三个参数,0600
mode_t umask(mode_t mask);
umask()将调用进程的文件权限创建掩码(umask)设置为mask & 0777(即:,只使用掩码的文件权限位),并返回之前的掩码值。
2.4 chmod
chmod可以更改权限,例如chmod 666 ./hellodup6, 更改成666权限
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
2.5 目录创建和销毁
增加文件目录和删除文件目录
mkdir
NAME
mkdir, mkdirat - create a directory
SYNOPSIS
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
rmdir
NAME
rmdir - delete a directory
SYNOPSIS
#include <unistd.h>
int rmdir(const char *pathname); //删除空目录
DESCRIPTION
rmdir() deletes a directory, which must be empty.
2.6 更改当前工作路径,获取
chdir() = cd
NAME
chdir, fchdir - change working directory
SYNOPSIS
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
DESCRIPTION
chdir() changes the current working directory of the calling process to the directory specified in path.
fchdir() is identical to chdir(); the only difference is that the directory is given as an open file descriptor.
获取当前路径,getcwd()= pwd
NAME
getcwd, getwd, get_current_dir_name - get current working directory
SYNOPSIS
#include <unistd.h>
char *getcwd(char *buf, size_t size);
输入参数buf为指定字符串,以及size长度,如果当前目录超过size,则报错。如果buf写NULL,则会自动malloc,需要free
注意,成功时,buf为当前目录
使用getcwd与readdir,opendir,获取当前目录下所有文件
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
#define BUFSIZE 50
int main(int argc,char** argv)
{
DIR* dir;
struct dirent* sdir;
char* pwd = NULL; //这里不能用char pwd[50]; pwd = getcwd(buf,BUFSIZE), 数组名是常量指针
char buf[BUFSIZE];
pwd = getcwd(buf,BUFSIZE); //如果是NULL,则会malloc,记得free
puts(buf);
dir = opendir(pwd);
if(dir == NULL)
{
perror("dir");
exit(1);
}
while(1)
{
sdir = readdir(dir);
if((sdir == NULL)&&(errno!= 0))
{
perror("readdir");
exit(1);
}
else if(sdir == NULL)
break;
puts(sdir->d_name);
}
closedir(dir);
exit(0);
}
2.7 分析目录与读取目录
可以用glob函数,获取某个目录下(通配符)的所有文件。
glob函数,使用通配符pattern,来指定目录,其与int argc char** argv类似./glob ./*.c
glob申请了gl_pathv的内存,需要搭配globfree函数来销毁
typedef struct {
size_t gl_pathc; /* Count of paths matched so far */
char **gl_pathv; /* List of matched pathnames. */
size_t gl_offs; /* Slots to reserve in gl_pathv. */
} glob_t
这里注意,glob_t.gl_pathc≈argc ,glob_t.gl_pathv≈argv
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#define PAT "./ *.c"
#define DIR "./ *" //为非隐藏,./.*为隐藏,这里不能用./来当做pattern参数
int main(int argc,char** argv)
{
glob_t globres,globdir;
int revalue = glob(PAT,0,NULL,&globres);
int rdvalue = glob(DIR,0,NULL,&globdir);
if(revalue)
{
printf("Errror code = %d\n",revalue);
exit(1);
}
if(rdvalue)
{
printf("Errror code = %d\n",rdvalue);
exit(1);
}
for(int i=0;i<globres.gl_pathc;i++)
{
printf("%s\n",globres.gl_pathv[i]);
}
puts("--------------------------------------");
for(int i=1;argv[i]!=NULL;i++)
{
printf("%s\n",argv[i]);
}
puts("--------------------------------------");
for(int i=0;i<globdir.gl_pathc;i++)
{
printf("%s\n",globdir.gl_pathv[i]);
}
globfree(revalue);
globfree(rdvalue);
exit(0);
}
上述两种获得当前路径文件的方法,glob与readdir等函数,其实际可能是利用了dentry结构中的双向链表遍历,同一层dentry的子目录来获取
2.8 目录流操作函数
文件操作与目录操作类似,均
DIR *opendir(const char *name); //打开目录流
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
void rewinddir(DIR *dirp); //复位目录流,回到头部
void seekdir(DIR *dirp, long loc); //设置目录流位置
long telldir(DIR *dirp); //告诉目录流当前位置
这里分析下readdir函数:
struct dirent *readdir(DIR *dirp);
返回值:正常为struct dirent *类型的指针,读到底为NULL(当errno不等于0时,在判断出错)
struct dirent *类型如下所示:
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below 这里d_off的与telldir是一样的,目录文件指针的位置*/
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename 文件名*/
};
d_type有如下8种类型,其中7种为基础文件类型
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type could not be determined.
使用目录流操作函数来获取某目录下的所有文件,
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
int main(int argc,char** argv)
{
DIR* dir;
struct dirent* sdir;
dir = opendir(argv[1]);
if(dir == NULL)
{
perror("dir");
exit(1);
}
while(1)
{
sdir = readdir(dir); //持续读,有目录流指针定位
if((sdir == NULL)&&(errno!= 0))
{
perror("readdir");
exit(1);
}
else if(sdir == NULL)
break;
puts(sdir->d_name);
}
closedir(dir);
exit(0);
}
3 open函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
flags可以如下设置
mode可以如下设置
open("hellodup5",O_RDWR|O_CREAT|O_TRUNC,0600);
0600 = user read 400 + write 200 ,其他为0
其中前三个表示文件拥有者的权限,中间三个表示文件所属组拥有的权限,最后三个表示其他用户拥有的权限。
4 lseek函数(与空洞文件)
#include <unistd.h>
off_t lseek( int filedes, off_t offset, int whence );
返回值:若成功则返回新的文件偏移量,若出错则返回-1。
- 按系统默认情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
- lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。然后,该偏移量用于下一个读或写操作。
- 文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞,文件中空洞均为0。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int fd;
fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0600);
if(fd<0)
{
perror("open()");
exit(1);
}
lseek(fd,5ll*1024ll*1024ll*1024ll-1ll,SEEK_SET);
write(fd,"",1); //系统调用,导致lseek偏移的并非为空洞文件,直接cp,会导致Blocks为0,为空洞文件
close(fd);
exit(0);
}
通过 cp操作后, 使用stat或者ls -l查看文件状态,发现其Blocks为0,因为cp发现其为空洞文件,则没有执行write操作
lseek = fseek + ftell , 先执行fseek,设置文件指针,后通过ftell获取文件指针
4 程序重定向(dup=duplication)
dup与dup2,fcntl,类似于echo “???”> PATH
int dup(int oldfd); //将oldfd的指针复制到当前可用的最小的
int fcntl(int fd, F_DUPFD, arg ); //fcntl(fd,F_DUPFD,0) = dup(fd),注意,要大于等于arg的最小值
Duplicate the file descriptor fd using the lowest-numbered
available file descriptor greater than or equal to arg. This is
different from dup2(2), which uses exactly the file descriptor
specified
int dup2(int oldfd, int newfd); //如果newfd已经打开,则close(newfd),再dup(oldfd),不再是最小的,而是newfd
如果oldfd = newfd的话,则dup2什么都不会做,返回newfd
dup(dup使用时,可能是非原子,使用dup2代替可以)
例子1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
FILE* fp;
//close(1);
fd = open("hellodup5",O_RDWR|O_CREAT|O_TRUNC,0600);
// fp = fopen("hellodup1","w+");
// fd = fileno(fp);
if(fd<0)
{ perror("fopen");
exit(1);
}
close(1);
dup(fd); //这两步可以用dup(fd,1)代替
if(fd != 1)
close(fd);
printf("helloworld\n");
exit(0);
}
例子2
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
FILE* fp;
//close(1);
fd = open("hellodup5",O_RDWR|O_CREAT|O_TRUNC,0600);
// fp = fopen("hellodup1","w+");
// fd = fileno(fp);
if(fd<0)
{ perror("fopen");
exit(1);
}
dup2(fd,1); //如果fd = 1,则dup2什么都不干
if(fd != 1)
close(fd); //则要判断fd是否为1,是1,则不close
printf("helloworld\n");
exit(0);
}
dup2,指向性替换fd
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
FILE* fp;
int fd;
fp = fopen("helloworld","w+");
if(fp==NULL)
{
perror("fopen");
exit(1);
}
fd = fileno(fp);
printf("fd = %d\n",fd);
//close(2);
int fdn = dup2(fd,2); //dup2是close(2),与dup(fd),的原子操作
printf("???????\n");
//fcntl(fd,F_DUPFD,1);
for(int i=0;i<10; i++)
{
fprintf(stderr,"hello+");
}
fflush(stderr);
printf("end/n");
fclose(fp);
exit(0);
}
本文地址:https://blog.csdn.net/weixin_44537992/article/details/107258860
上一篇: Python JSON
下一篇: python类的继承