目录
一、文件IO
系统调用
由操作系统实现并提供给外部应用程序的编程接口。是应用程序同系统之间数据交互的桥梁。与库函数的不同之处,库函数是程序库中的函数
open函数、close函数
#include <unistd.h>:包含man里面系统头文件
#include <fcntl.h>:包含flags宏定义
#include <errno.h>
/*参数:
pathname: 欲打开的文件路径名
flags:文件打开方式: #include <fcntl.h>
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1,设置errno,需引入头文件 errno.h
*/
int open(char *pathname, int flags)
/*
参数:
pathname: 欲打开的文件路径名
flags:文件打开方式:
O_RDONLY|O_WRONLY|O_RDWR
O_CREAT|O_APPEND|O_TRUNC|O_EXCL|O_NONBLOCK ....
mode: 参数3使用的前提, 参2指定了 O_CREAT。
取值8进制数,用来描述文件的 访问权限。 rwx(r:4,w:2,x:1) 0664
创建文件最终权限 = mode & ~umask
返回值:
成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
*/
int open(char *pathname, int flags, mode_t mode)
//关闭文件描述符
int close(int fd);
//strerror函数:输出错误提示语,传入errno
char *strerror(int errnum);
read函数、write函数
#include <unistd.h>
/*
参数:
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小
返回值:
0: 读到文件末尾。
成功; > 0 读到的字节数。
失败: -1, 设置 errno
-1: 并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞
方式读一个设备文件(网络文件),并且文件无数据。
*/
ssize_t read(int fd, void *buf, size_t count);
#include <unistd.h>
/*
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:
成功:写入的字节数。
失败:-1, 设置 errno
*/
ssize_t write(int fd, const void *buf, size_t count);
perror函数(打印异常)
#include <stdio.h>
void perror(const char *s);
系统调用和库函数比较—预读入缓输出
fputc/fgetc实现cp
read/write实现cp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc , char* argv[]){
char buf[1024];
int n = 0;
int fd1 = open(argv[1], O_RDONLY);
int fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC,0664);
while((n = read(fd1,buf,1024)) != 0){
write(fd2, buf,n);
}
close(fd1);
close(fd2);
return 0;
}
实验结果:read/write速度慢
原因分析:
<1> read/write这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
<2> fgetc/fputc,有个缓冲区,4096,所以它并不是一个字节一个字节地写,内核和用户切换就比较少预读入,缓输出机制。
<3> 标准IO函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。
文件描述符
文件描述符是指向一个文件结构体的指针。PCB进程控制块:本质 结构体。
成员:文件描述符表。
文件描述符:0/1/2/3/4。。。。/1023 表中可用的最小的。
0 -STDIN_FILENO 键盘
1 - STDOUT_FILENO 显示器
2 - STDERR_FILENO
阻塞和非阻塞
产生阻塞的场景:读设备文件。读网络文件的属性。(读常规文件无阻塞概念。)
/dev/tty -- 终端文件(属于设备文件)。
open("/dev/tty", O_RDWR | O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
更改非阻塞读取终端——超时设置
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"
int main(void)
{
//打开文件
int fd, n, i;
fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
if(fd < 0){
perror("open /dev/tty");
exit(1);
}
printf("open /dev/tty ok... %d\n", fd);
//轮询读取
char buf[10];
for (i = 0; i < 5; i++){
n = read(fd, buf, 10);
if (n > 0) { //说明读到了东西
break;
}
if (errno != EAGAIN) { //EWOULDBLOCK
perror("read /dev/tty");
exit(1);
} else {
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
sleep(2);
}
}
//超时判断
if (i == 5) {
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
} else {
write(STDOUT_FILENO, buf, n);
}
//关闭文件
close(fd);
return 0;
}
fcntl改文件属性
fcntl用来改变一个【已经打开】的文件的 访问控制属性
重点掌握两个参数的使用, F_GETFL,F_SETFL
#include <unistd.h>
#include <fcntl.h>
/*
参数:
fd 文件描述符
cmd 命令,决定了后续参数个数
获取文件状态: F_GETFL
设置文件状态: F_SETFL
返回值:
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
*/
int fcntl(int fd, int cmd, ... /* arg */ );
终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int flags, n;
flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
if(flags == -1){
perror("fcntl error");
exit(1);
}
flags |= O_NONBLOCK;
int ret = fcntl(STDIN_FILENO, F_SETFL, flags);
if(ret == -1){
perror("fcntl error");
exit(1);
}
tryagain:
n = read(STDIN_FILENO, buf, 10);
if(n < 0){
if(errno != EAGAIN){
perror("read /dev/tty");
exit(1);
}
sleep(3);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
write(STDOUT_FILENO, buf, n);
return 0;
}
lseek函数
#include <sys/types.h>
#include <unistd.h>
/*
参数:
fd:文件描述符
offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位
whence:起始偏移位置
SEEK_SET / SEEK_CUR / SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
*/
off_t lseek(int fd, off_t offset, int whence);
lseek示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(void)
{
int fd, n;
char msg[] = "It's a test for lseek\n";
char ch;
fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);
if(fd < 0){
perror("open lseek.txt error");
exit(1);
}
write(fd, msg, strlen(msg)); //使用fd对打开的文件进行写操作,问价读写位置位于文件结尾处。
lseek(fd, 0, SEEK_SET); //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢?
while((n = read(fd, &ch, 1))){
if(n < 0){
perror("read error");
exit(1);
}
write(STDOUT_FILENO, &ch, n); //将文件内容按字节读出,写出到屏幕
}
close(fd);
return 0;
}
应用场景:
1. 文件的“读”、“写”使用同一偏移位置。
2. 使用lseek获取文件大小(返回值接收)
lseek(fd , 0, SEEK_END);
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须【引起IO操作】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc,char*argv[]){
int fd = opne(argv[1], O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
int lenth = lsek(fd, 101, SEEK_END);
printf("file size: %d\n", lenth);
write(fd, "$", 1);
close(fd);
return 0;
}
简单小结一下:
1、对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。
2、lseek读取的文件大小总是相对文件头部而言。
3、用lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799,ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号,末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。
truncate函数
#include <unistd.h>
#include <sys/types.h>
//直接拓展文件大小
int truncate(const char *path, off_t length);
例:int ret = truncate("dict.cp", 250);
二、文件系统
传入传出参数
传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。
传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值。
目录项和inode
一个文件主要由两部分组成,dentry(目录项)和inode
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。具体可通过man 7 inode
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
文件操作
stat函数!!!
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/*
参数:
path: 文件路径
buf:(传出参数) 存放文件属性,inode结构体指针。
返回值:
成功: 0
失败: -1 errno
*/
//会存在符号穿透
int stat(const char *pathname, struct stat *statbuf);
//不会存在符号穿透
int lstat(const char *pathname, struct stat *statbuf);
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#icnlude <sys/stat.h>
int main(int argc, char *argv[]){
struct stat sbuf;
int ret = stat(argv[1],&sbuf);
if(ret == -1){
perror("stat error");
exit(1);
}
printf("file size: %ld\n",sbuf.st_size);
return 0;
}
lstat查看文件类型
int main(int argc,char *argv[]){
struct stat sb;
int ret = lstat(argv[1],&sb);
if(S_ISREG(sb.st_mode)){
printf("It is a regular\n");
}else if(S_ISDIR(sb.st_mode)){
printf("It is a dir\n");
}else if(S_ISFIFO(sb.st_mode)){
printf("It is a pipe\n");
}else if(S_ISLNK(sb.st_mode)){
printf("It is a sym link\n"); //无法显示符号连接
} //stat存在符号穿透 显示的是符号连接的文件
return 0;
}
link和unlink函数
硬链接数就是dentry数目
link就是用来创建硬链接的
link可以用来实现mv命令
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
用这个来实现mv或者rename,用oldpath来创建newpath,完事儿删除oldpath就行。
int unlink(const char *pathname);
unlink是删除一个文件的目录项dentry,使【硬链接数-1】
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,
但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑
时间将该文件释放掉。
/*
*unlink函数是删除一个dentry
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void) {
int fd, ret;
char *p = "test of unlink\n";
char *p2 = "after write something.\n";
fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open temp error");
exit(1);
}
ret = unlink("temp.txt"); //具备了被释放的条件
if(ret < 0){
perror("unlink error");
exit(1);
}
ret = write(fd, p, strlen(p));
if (ret == -1) {
perror("-----write error");
}
printf("hi! I'm printf\n");
ret = write(fd, p2, strlen(p2));
if (ret == -1) {
perror("-----write error");
}
printf("Enter anykey continue\n");
getchar();
close(fd);
return 0;
}
隐式回收:
当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。
文件目录rwx权限差异
目录操作函数
//成功返回指向该目录结构体指针,失败返回 NULL
DIR * opendir(char *name);
//成功:0;失败:-1 设置 errno 为相应值
int closedir(DIR *dp);
/*
成功返回目录项结构体指针;失败返回NULL设置errno为相应值
需注意返回值,读取数据结束时也返回 NULL 值,所以应借助 errno 进一步加以区分。
*/
struct dirent *readdir(DIR * dp);
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
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 */
};
实现ls
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <pthread.h>
int main (int argc, char*argv[]){
DIR* sdir;
struct dirent* sd;
sdir = opendir(argv[1]);
while((sd = readdir(sdir))!=NULL){
/*
strcmp比较两个字符串的大小,一个字符一个字符比较,按ASCLL码比较
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
*/
if(strcmp(sd->d_name,".")==0||strcmp(sd->d_name,"..")==0)
continue;
printf("%s\t",sd->d_name);
};
printf("\n");
closedir(sdir);
}
递归遍历目录!!!
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <dirent.h>
void isFile(char *name);
void read_dir(char* dir){
char path[256];
DIR* dp;
struct dirent *sdp;
dp = opendir(dir);
if(dp == NULL){
perror("opendir error");
return;
}
while((sdp = readdir(dp)) != NULL){
if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0){
continue;
}
sprintf(path,"%s/%s", dir, sdp->d_name);
isFile(path);
}
closedir(dp);
}
void isFile(char *name){
int ret = 0;
struct stat sb;
ret = stat(name, &sb);
if(ret == -1){
perror("stat error");
return ;
}
if(S_ISDIR(sb.st_mode)){
read_dir(name);
}
printf("%s\t%ld\n",name,sb.st_size);
return;
}
int main(int argc, char* argv[]){
if(argc == 1){
isFile(".");
}else{
while(--argc > 0) //可一次查询多个目录
isFile(*++argv); //循环调用该函数处理各个目录
}
return 0;
}
dup和dup2函数
#include <unistd.h>
//文件描述符复制
int dup(int oldfd);
oldfd: 已有文件描述符
返回:新文件描述符,这个描述符和oldfd指向相同内容。
//文件描述符复制,oldfd拷贝给newfd。返回newfd ,但newfd全指向oldfd指向的结构体
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc,char*argv[]){
int fd1 = open(argv[1],O_RDWR);
int fd2 = open(argv[2],O_RDWR);
int fdret = dup2(fd1,fd2);
write(fd2,"1234567", 7);
dup2(fd1,STDOUT_FILENO);
return 0;
}
注意:打开一个文件,读写指针默认在文件头,如果文件本身有内容,直接写入会覆盖原有内容。
fcntl实现dup描述符
int fcntl(int fd, int cmd, ....);
cmd: F_DUPFD
参3: 被占用的,返回最小可用的。
未被占用的, 返回=该值的文件描述符。
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc,char*argv[]){
int fd1 = open(argv[1],O_RDWR);
int fd2 = open(argv[2],O_RDWR);
int newfd = fcntl(fd1,F_DUPFD,0);
write(newfd,"1234567", 7);
return 0;
}