你只要记住个系统调用的名字,然后到man手册的第2卷去查就行
查看umask
在终端输入umask,返回八进制的umask
错误处理函数
系统会设置一个默认的errno全局变量表示当前发生的错误,但是这个数字我们看不懂,用错误处理函数char* strerror(int errnum)进行翻译。
例如:printf("xxx error: %s\n",strerror(errno));
open
两种参数填入方法,返回文件描述符,文件描述符为-1表示失败
int open(const char* pathname,int flags);int open(const char* pathname,int flags,mode_t mode);
flags是打开参数,常见的打开参数:
- O_RDONLY 只读
- O_WRONLY 只写
- O_RDWR 读写
- O_TRUNC 截断为0=清空
- O_CREAT 创建
- O_NONBLOCK 非阻塞方式
mode是权限,因为只有创建文件才需要权限,所以3参数的填入方法适用于需要创建文件的场景。
例如0567,其中0是表示八进制。
权限 = mode & ~umask,但是一般不用考虑,除非你发现权限和你设置mode不一样。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int fd;
fd=open("./dict.cpp",O_RDONLY | O_CREAT | O_TRUNC,0775);
printf("fd=%d\n",fd);
close(fd);
return 0;
}
close
int close(int fd)
read 和 write
read
size_t read(int fd,void *buf,size_t count);
参数:
- fd 文件描述符
- buf 存数据的缓冲区
- count 缓冲区大小
返回值:
- 成功:读到的字节数
- 失败:-1,设置errno
write
size_t read(int fd,const void *buf,size_t count);
参数:
- fd 文件描述符
- buf 将被写出数据的缓冲区
- count 写出数据的大小
返回值:
- 成功:写入的字节数
- 失败:-1,设置errno
- -1:并且errno = EAGAIN 或者 EWOULDBLOCK,说明不是读取失败,而是read在非阻塞方式读一个设备文件或者网络文件时,文件无数据。你阻塞方式阻塞的时候他会等待输入,你非阻塞方式阻塞他直接就走完了,总得给个提示。
使用read和write实现cp命令
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char* argv[]){
int fd1=open(argv[1],O_RDONLY); //以只读形式打开文件
int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0775); //文件存在则清空文件后使用可读写的方式打开文件,否则创建文件
char buff[1024]; //设置缓冲区
int size=0;
while((size=read(fd1,buff,1024))!=0){ //如果依然还能读出数据就继续
write(fd2,buff,size); //将取出的数据存入缓冲区
}
close(fd1); //关闭文件1
close(fd2); //关闭文件2
return 0;
}
阻塞和非阻塞
产生场景:读设备(总线),读网络文件(网速),读常规文件不阻塞。 常见设备文件:
- STDIN_FILENO
- STDOUT_FILENO
- /dev/tty(终端)
但是说实话你标准输出是往终端里输,显示也是终端里显示
//从标准输入中读出数据到标准输出,就是从键盘读取显示到屏幕,你键盘等待输入就是阻塞
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(){
char buf[10];
int n;
n=read(STDIN_FILENO,buf,10);
if(n<0){
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO,buf,n);
return 0;
}
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#define MSG_TRY "try again\n" //重试信息
#define MSG_TIMEOUT "time out\n" //超时信息
int main(){
char buf[10];
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);
//读文件的时候才和非阻塞状态有关
for(i=0;i<5;i++){ //超时计时器
n=read(fd,buf,10);
if(n>0){ //读取成功
break;
}
//n<0,阻塞状态下有两种可能,一种是读取失败。另一种是文件为空,但是不等待阻塞。
if(errno!=EAGAIN){ //读取失败那就是错了
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
file control 文件控制函数,用于改变文件属性
返回值:位图
位图:每个bit是一个控制位/标志位,这样表示节省内存,然后一般和一个什么掩码做一个逻辑运算,就可以改变控制位,相当于改变系统的状态
int flags=fcntl(0,F_GETFL);
flags |= O_NONBLOCK; //按位或,反正不管你原来多少,都能设置为非阻塞状态
fcntl(0,F_SETFL,flags) //再把控制位写回去,实现改变属性
lseek
off_t lseek(int fd,off_t offset,int whence);
参数:
- fd:文件描述符
- offset:偏移量
- whence:起始偏移位置:SEEK_SET/SEEK_CUR/SEEK_END
返回值:
- 成功:返回当前相对于起始位置的偏移量
- 失败:-1,设置errno
文件的读和写共用一个偏移,就是你读写不会重置光标
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(){
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));
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;
}
int size=lseek(fd,0,SEEK_END);
因为你返回的是相对于起始位置的偏移量,所以直接就是文件大小
文件描述符
PCB进程控制块:本质 结构体
成员:文件描述符表
文件描述符表:0/1/2/3...1023,表示当前进程打开的文件
文件描述符指向一个描述文件的结构体
你打开文件会从3开始赋值,关闭文件就会删除,然后他默认从可用的最小的开始赋值
其中0,1,2默认对应宏STDIN_FILENO,STDOUT_FILENO,STDERR_FILEBO
inode和dentry
inode
ls -l显示的那个是innode号,指向inode,inode实际上是一个储存了文件信息的结构体
你ls -l 显示出来的东西基本就是inode里读出来的。
inode号和文件描述符一个原理
dentry
directory entry 目录项
dentry = 文件名+inode号
两者的关系
所以就是dentry的inode号 -> inode -> 磁盘
那么创建硬链接实际上就是创建不同的dentry,拥有相同的inode号
传入和传出参数
反正就是用指针直接改变本体那一套
传入参数 const &
- 指针作为函数参数
- 通常有const关键字修饰
- 指针指向有效区域,在函数内部做读操作
传出参数 用指针接返回值
- 指针作为函数参数
- 在函数调用之前,指针指向的空间无意义,但是必须有效
- 在函数内部,做写操作
- 函数调用结束后,充当函数返回值
传入传出参数 改变本体
- 指针作为函数参数
- 在函数调用之前,指针指向的空间有意义
- 在函数内部,先读后写
- 函数调用结束后,充当函数返回值
stat和lstat
int stat(const char* path,struct stat *buf);
参数:
path:文件路径buf:传出参数,存放文件属性
返回值:
- 成功:0
- 失败:-1,设置errno
获取文件大小:buf.st_size
获取文件类型:buf.st_mode
获取文件权限:buf.st_mode
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc,char *argv[]){
struct stat stbuf;
int ret;
//获取存放文件属性的结构体
ret=stat(argv[1],&stbuf);
if(ret==-1){
perror("stat error");
exit(1);
}
//利用宏函数判断文件类型,里面估计就是什么掩码按位操作之类的
if(S_ISREG(stbuf.st_mode)){
printf("It is a regular\n");
}else if(S_ISDIR(stbuf.st_mode)){
printf("It is a directory\n");
}else if(S_ISFIFO(stbuf.st_mode)){
printf("It is a pipe\n");
}else if(S_ISLNK(stbuf.st_mode)){
//stat函数会穿透符号链接,就是说他会去直接查指向的那一个文件,而不是链接本身
//想要查符号链接本身需要lstat
//符号链接=软链接
printf("It is a sym link\n");
}
//获得文件大小
printf("file size:%lld\n",stbuf.st_size);
return 0;
}
link和unlink
link创建硬链接
unlink删除硬链接,当计数器为0的时候,等待所有打开该文件的进程关闭后,系统就会释放该链接,相当于删除文件 所以这东西也可以用来删除文件
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(){
int fd;
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);
}
//当前计数值=0,但是文件没关闭之前你释放不了,这里相当于给了一个承诺
//等所有打开该文件的进程结束了,才会释放
int 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();
//这里尝试修改字符串常量,发生段错误
//但是如果这样,fd是不是关闭不了,那么fd=3被占用,下次就是fd=4,5,...,1023,总有一天会爆
//隐式回收机制,如果进程结束,打开的文件,申请的内存之类的会自己释放
//const char *
p[3]='H';
close(fd);
return 0;
}