前面我们说到,C库函数底层是调用了Linux系统函数的。
而且在讲到虚拟地址空间的时候也说到,用户是不能够直接操作内核空间的,如果想要堆内核空间进行读写可以使用系统调用。系统调用其实就是调用一些系统api
最常用的四个(必须要很清晰的知道的)
int open(const char* pathname,int flags,mode_t mask); //重点掌握 flags和mask
size_t read(int fd,void *buf,size_t count); //重点掌握返回值
size_t write(int fd,void *buf,size_t count);
int fcntl(int fd,int cmd,.../*arg*/); //重点掌握阻塞和非阻塞的设置
我们学习api不是说我们一定要把他们背下来,api(程序应用接口)很多很多,未来我们遇见的会更多更多,我们要学会查文档,查资料。
Linux给我们提供了man文档,当然作者并不是特别喜欢。一者我觉得里面都是英文不方便看,二者里面有很多废话。这里我们也介绍下怎么使用吧
man手册第一章是一些常见的命令,第二章是Linux系统的函数,之后C库函数...
man 章节 函数名 / man 函数名
vim如何进行字符串匹配呢?
/加上需要匹配的内容
/return value
第二种方法:
我们通常使用vscode进行开发
直接相应位置右击查看定义即可
正式介绍各个api了
error介绍
error是一个全局变量,定义在头文件error.h中。它总是会记录系统最后一次出现错误的值(这个值是一个int类型)。每一个int类型都定义一个字符串描述的字符串。
这些宏定义都放在了
/usr/include/asm-generic/errno-bash.h 1-34
/usr/include/asm-generic/errno.h 35-133
perror()
头文件:stdio.h
原型: void perror(const char*s);
描述:根据error对应的字符串加上直接的描述输出到标准错误
#include <iostream> #include <error.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> using namespace std; int main(void) { int fd = open("a.txt",O_RDONLY); perror("file"); close(fd); return 0; }
open()
头文件:
#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); //创建新文件使用
参数说明:
pathname:要创建的文件路径
flags:对文件的操作权限
必选项-->O_RDONLY O_RDWR O_WRONLY 这三个互斥
可选项-->O_CREATE
flags是一个int类型的数据。占4个字节,32位。每一个位都是一个标志位(或者说每一个位都代表了一种权限)
它们按位 或 的方式进行计算(如此以来就能够进行权限的累加)
比如 第0位表示写 第1位表示读 那么可读可写为:0000000..00011
mode:表示创建出来的新的文件的操作权限(通常用八进制数表示)
xxx 分别表示 读写执行 三个权限
在Linux中对于一个文件的操作通常分为三种权限: 自己 同一个组的人 其他人
所以正好是 三个 xxx
xxx xxx xxx 分别表示自己 同组的人 其他人
我们知道二进制转八进制正好是对应三个位,如此一来只需要提前提前准备一个转换的关系表就可以很快的计算出相应的权限。111-->7 110-->6
如果直接使用二进制表示的话,是不是感觉太长了而且还容易弄错。
正好八进制可以很快的进行转换,所以使用八进制很nice
最终的权限:mode & umask(0775)
为什么要这么规定呢?这不是徒增麻烦嘛
当然需要这么规定,为了限制一些权限。防止数据被瞎改
比如: 111 111 111(0777) & 111 111 101(0775) = 111 111 101(0775)也就是限制了其他人写的操作,当然 umask是可以修改的
补充:如何修改程序中的 mask掩码
mode_t umask(mode_t mask);
返回值:-1表示错误
#include <iostream> #include <error.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> using namespace std; int main(void) { int fd = open("a.txt",O_RDWR|O_CREAT); if(fd == -1) { perror("file open"); } close(fd); return 0; }
read()和write()
read()===>
头文件: #include <unistd.h>
原 型: ssize_t read(int fd,void *buf,size_t count); //将数据从fd指向的文件中读到buf中
参数说明:
fd 文件描述符
buf 暂存数据的buf(主调函数分配内存,通常为char *)
count buf的大小(通常传sizeof(buf))
返回值:
-1 ==>读文件失败
0 ==>数据读完了(通常我们通过0来判断对方是否已经传递完数据)
>0 ==>读取的字节数
write()===>
头文件: #include <unistd.h>
原 型: ssize_t write(int fd,void *buf,size_t count); //把buf里的数据写入到fd指定的文件中
参数说明:
fd 文件描述符
buf 暂存数据的buf(主调函数分配内存)
count buf的大小(通常传sizeof(buf))
返回值:
-1 ===>写文件失败
0 ===>数据写完了
>0 ===>写入到文件中的数据大小
强烈安利这个插件,可以直接运行/调试
#include <iostream> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> using namespace std; int main(void) { char buf[1024] = {0}; //打开一个已存在文件 int fd = open("a.txt",O_RDONLY); if(fd == -1) { perror("a.txt open"); exit(-1); } //打开一个拷贝的目的文件,不存在则创建 int fd1 = open("copya.txt",O_RDWR|O_CREAT,0777); if(fd1 == -1) { perror("copya.txt open"); exit(-1); } //开始开吧文件了 int count = 0; count = read(fd,buf,sizeof(buf)); if(count == -1) { perror("read"); exit(-1); } while (count) //没读完一直读 { int ret = write(fd1,buf,count); printf("write byte %d\n",ret); //继续读 count = read(fd,buf,sizeof(buf)); } close(fd); close(fd1); return 0; }
fcntl()
头文件:
#include <unistd.h>
#include <fcntl.h>原 型:
int fcntl(int fd, int cmd, ... /* arg */ );
参数说明:
fd 文件描述符
cmd 表示对文件描述符进行如何操作
arg 可变参数
cmd:(duplicate 复制)
F_DUPFD --> 复制的文件一个文件描述符,得到一个新的文件描述符
(复制的意思是指从文件描述符表中找一个空闲的最小的指向同一个文件,而不是将fd的值复制一份)
int fd1 = fcntl(fd,F_DUPFD);
F_GETFL --> 获取指定文件描述符文件状态flag
F_SETFL --> 设置文件描述符状态flag
(flag与open函数的第二个参数一样的值)
返回值:
失败返回-1,并设置error
成功返回一个新的文件描述符
#include <iostream> #include <unistd.h> #include <fcntl.h> using namespace std; int main(void) { int fd = open("copya.txt",O_RDWR); if(fd == -1) { perror("copya.txt open"); exit(-1); } //设置阻塞IO int flag = fcntl(fd,F_GETFL,0); flag |= O_NONBLOCK; //设置为非阻塞 int res = fcntl(fd,F_SETFL,flag); if(res < 0) { perror("非阻塞"); exit(-1); } //去除阻塞状态 flag = fcntl(fd,F_GETFL,0); flag &= ~O_NONBLOCK; fcntl(fd,F_SETFL,flag); return 0; }
上面四个函数是必须牢记于心的,下面还要一些函数,需要大家有了解,需要用的时候区查找
int dup(int oldfd); //复制一个文件描述符,返回值返回新的文件描述符
int dup2(int oldfd,int newfd); //重定义一个文件描述符
oldfd 指向 a.txt newfd指向 b.txt
调用完之后,oldfd和newfd都指向 a.txt
int access(const char *pathname,int mode);
//判断某个文件是否具有某个权限,判断文件是否存在
mode:
R_OK W_OK X_OK F_OK
返回值:成功返回0 失败-1
int truncate(const char*path,off_t length); //减缩或者扩展文件尺寸
成功返回0 失败-1
off_t lseek(int fd,off_t offset,int whence); //移动文件读写指针
fd:文件描述符
offset:需要偏移的大小
whence:从什么地方开始偏移
返回值:文件读写指针的位置
SEEK_SET 头
SEEK_END 尾
SEEK_CUR 当前位置
移动文件读写指针到文件头
lseek(fd,0,SEEK_SET);
获取当前文件读写指针的位置
lseek(fd,0,SEEK_CUR)
获取文件长度
lseek(fd,0,SEEK_END)
拓展文件的长度
lseek(fd,1000,SEEK_END) 一定要写入一个字符才能生效 write(fd,"",1);