1. 系统调用概念:
库函数-> 内核函数[系统调用] -> 驱动[磁盘、显示器]
int main(){
int a=10; // 在用户空间执行
printf("%s\n","hello kernel"); // 系统调用进入内核,在内核执行
int b=10; // 回到用户空间
return 0
}
每个进程打开文件都要打开这3个设备:
0:标准输入对应文件描述符0 stdin
1:标准输出 stdout
2:标准出错 stderror
返回值:
成功 返回文件描述符
失败 -1
2. 基本系统调用IO函数
2.1.open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(){
/*
使用 man 2 open:可以自己去看文档,一定要学会看文档,系统调用在第二卷
库函数在第3卷
参数2:
O_RDONLY:只读方式打开文件。
O_WRONLY:可写方式打开文件。
O_RDWR:读写方式打开文件。
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限(读写执行权限)。
O_EXCL:如果使用O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在。
O_TRUNC:如文件已经存在,那么打开文件时先删除文件中原有数据。
O_APPEND:以添加方式打开文件,所以对文件的写操作都在文件的末尾进行。//write 文件开始位置开始写
参数3:
0666 & umask(0002)[文件的实际权限 0666 & umask取反] 就是 0664 创建的文件权限
110 110 110 0666
111 111 101 000 000 010 0002
110 110 100 664
参数也可以使用枚举man 2 open
返回值:
-1:表示失败, 成功:返回文件描述符
如何open函数执行失败如何查看具体错误:
方式1:使用:perror("open failed"); 里面是自定义错误,具体错误errno头文件中,都会打出来
方法2: 使用 printf("%s\n",strerror(errno));
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
函数creat:creat("test.txt", 0666); // open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
*/
int fd;
fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH,0666);
if(-1 == fd)
{
//printf("%s\n",strerror(errno));
perror("open failed");
// errno.h linux内部对错误状态码封装到这个头文件中,解析错误状态码返回字符串:strerror
// man errno
}
close(fd);//1024
return 0;
}
2.2. read、write、lseek 函数使用
linux不区分文本流和二进制流
文本流:
字符以ASCII 方式存储
文本流写入 \n 会被转化为 回车和换行对应ASCII 0DH和OAH
输出的时候,0DH和0AH会被转化为\n
二进制流:
以二进制0001存储,\n会被原本存储
========================
标准IO:支持标准c库的系统都可以
系统读取文件,首先读取到缓冲区中,然后进程去缓冲区读取
写
文件IO:无缓冲区, 实现posix规范的系统都可以
缓冲分类:(标准IO)
全缓冲: 写满缓冲区才写入磁盘,linux 4096,fopen也是使用输入文件中
刷新机制:缓冲区写满、调用fflush
行缓冲: 遇到\n写入磁盘,标准输入、输出使用,默认1024个字节
刷新机制:\n、读满1024个字节、fflush
无缓冲区: 标准出错使用
为什么需要缓冲:减少系统调用次数,提高效率
缓冲区是通过malloc申请的空间
申请的实际是发生在I/O操作的时候
比如:
printf("hello world")
while(1){}
//行缓冲,不会输出hello world
如何输出:
1.添加\n
2.进程结束
3.添加 fflush(stdout);
函数api:
#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
flag: 必选
O_RDONLY, O_WRONLY, or O_RDWR. 读 写 读写
可选:
O_APPEND 追加
O_CREAT 创建
mode 权限位 最终( mode & ~umask)
O_NONBLOCK 非阻塞
返回值: 放回最小可用文件描述符
close : 关闭文件描述符
- 1 : 失败 0 成功
#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
flag: 必选
O_RDONLY, O_WRONLY, or O_RDWR. 读 写 读写
可选:
O_APPEND 追加
O_CREAT 创建
mode 权限位 最终( mode & ~umask)
O_NONBLOCK 非阻塞
返回值: 放回最小可用文件描述符
close : 关闭文件描述符
- 1 : 失败 0 成功
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
int main000(int argc, const char *argv[])
{
int fd;
fd = open("test.txt", O_RDWR | O_CREAT| O_APPEND , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(-1 == fd)
{
perror("open failed1");
return -1;
}
char *writebuf = "\nwrite context 0";
printf("%ld\n", strlen(writebuf));
long int writeCount = -1;
writeCount = write(fd, writebuf, strlen(writebuf));
if (-1 == writeCount) {
perror("write failed");
return -1;
}
// 写入内容以后,文件指针偏移到末尾去了,后面无法读取内容,把指针偏移到头部来
// -1 失败 成功:返回较起始位置偏移量
// 1. lseek偏移
lseek(fd,SEEK_SET,0);
char buf[1024] = { 0 };
long int count = 0;
long unsigned int readsize = 20;
// 如果count == 0 那么读到末尾了
while ((count = read(fd, buf, readsize)) > 0) {
if (count == -1) {
perror("read failed");
return -1;
}
printf("----%s\n", buf);
memset(buf, 0, sizeof(buf));
}
lseek(fd,0,SEEK_SET);
// 2.通过lseek获取文件大小
int length=lseek(fd,0,SEEK_END);
printf("filesize1---%d\n",length);
// 3. lseek制造文件空洞
// 文件空洞[偏移的1000个字节叫做空洞],必须在最后添加\0引起IO操做
// 空洞文件:造空洞,下载的时候多开几个进程去下载
length=lseek(fd,1000,SEEK_CUR);
printf("filesize2---%d\n",length);
// 扩张空洞文件,必须要写一次
write(fd,"q",1);
close(fd);
// fd = open("test.txt", O_RDWR | O_CREAT| O_APPEND , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
// if(-1 == fd)
// {
// perror("open failed2");
// return -1;
// }
close(fd);//1024
}
问题1: 使用库函数fgetc 和 系统调用read函数哪个拷贝内容快,每次读写1个字节
使用fgetc快,如果使用fgetc 那么首先用户态写入内存态,写满4096个字节,才一次写入磁盘但是如果使用 read, 每次读一个字节写入磁盘一个字节,速度慢,这样的话
问题2: 文件描述符内核理解
pcb进程控制块: 本质结构体
成员: 文件描述符表
文件描述符表,一个进程可以打开1024个文件
0 1 2:标准输入|输出|出错 文件描述符,每一个int对应一个结构体,每次打开一个文件,分配一个int
3. 阻塞概念、fcntl函数
阻塞:只有设备文件[dev/tty,终端输入文件]和网络存在阻塞问题,普通文件不存在阻塞问题
3.1.等待解决阻塞
STDIN_FILENO:接收键盘的输入
STDOUT_FILENO:向屏幕输出
https://blog.csdn.net/sinat_25457161/article/details/48548231
/**
* 等待解决阻塞
* /dev/tty 下文件默认是阻塞
*/
int main002(int argc, const char *argv[]){
char buf[10];
int n;
// /dev/tty 下文件默认是阻塞,程序停留在这里
n= read(STDIN_FILENO,buf,10);
if(n<0){
perror("read STDIN_FILEND");
exit(1);
}
write(STDIN_FILENO,buf,n);
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, const char *argv[]){
int fd = open("/dev/tty",O_RDWR);
char buf[10];
int n =0 ;
while(1){
// /dev/tty 下文件默认是阻塞,程序停留在这里
n= read(fd,buf,10);
if(n<0){
perror("read STDIN_FILEND");
exit(1);
}
// write(STDIN_FILENO,buf,n);
printf("---%s\n", buf);
sleep(1);
}
close(fd);
return 0;
}
3.2.轮寻解决阻塞、fcntl函数
/**
* fcntl函数: 文件属性控制函数,可以修改文件属性,比如文件可读、可写、文件阻塞
* 获取文件状态: F_GETFL
* 设置文件状态: F_SETFL
* fcntl (int __fd, int __cmd, ...)
* 轮寻解决阻塞
* 阻塞和非阻塞:
* 产生阻塞场景:读设备文件、读网络文件(读常规文件无阻塞概念)
* /dev/tty: 终端设备文件
*/
#define MSG_TRY "try agin\n"
int main003(int argc, const char *argv[]){
char buf[10];
int flags,n;
flags= fcntl(STDIN_FILENO,F_GETFL); // 获取stdin属性的信息
if(flags == -1){
perror("fcntl error");
exit(1);
}
// 这里是int 32个bit 位的位运算
flags = flags|O_NONBLOCK;
// 把/dev/tty从阻塞状态设置成非阻塞状态
int ret= fcntl(STDIN_FILENO,F_SETFL,flags);
if(ret == -1){
perror("fcntl error");
exit(1);
}
tryagain:
/**
* >0 实际督导字节数
* 0 读到末尾(对端已经关闭)
* -1: 读取失败 应该进一步判断errno的值
errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
errno = EINTR 慢速系统调用被 中断
errno =" 其他情况" 异常
* /dev/tty 读取非阻塞状态文件内容为空,也返回-1,但是此时errno==EAGAIN,此时轮读取内容
* 读取文件失败的时候也返回-1 ,
* 如果 errno==EAGAIN 表示读取的是 非阻塞文件返回 -1 , 不是读取读取文件失败
*/
n = read(STDIN_FILENO,buf,10);
if(n<0){
if(errno != EAGAIN){
perror("read /dev/tty");
exit(1);
}
sleep(3);
write(STDIN_FILENO,MSG_TRY,strlen(MSG_TRY));
goto tryagain;
}
write(STDIN_FILENO,buf,n);
return 0;
}
4. stat函数、 dup2 文件流重定向
4.1. stat函数:保存文件属性api类
int main004(int argc, const char *argv[])
{
struct stat st;
int ret=stat("test.txt",&st);
if(ret == -1){
perror("stat error");
exit(-1);
}
printf("文件大小:%ld\n",st.st_size);
// 判断是否为文件
if(S_ISREG(st.st_mode)){
printf("it is regular file\n");
}else if(S_ISDIR(st.st_mode)){
printf("it is directory file\n");
}else if(S_ISFIFO(st.st_mode)){
}
// stat函数无法识别软连接,需要使用
// 使用宏函数判断
struct stat lst;
lstat("test.soft",&lst);
// if (S_ISREG(lst.st_mode)) {
// printf("it is regular file");
// } else if (S_ISDIR(lst.st_mode)) {
// printf("it is directory file");
// } else if (S_ISLNK(lst.st_mode)) {
// printf("it is soft file");
// }
// 使用位运算 判断文件类型,具体如何进行位运算,看下图,文件的16位掩码
switch (lst.st_mode & S_IFMT) {
case S_IFBLK:
printf("block device\n");
break;
case S_IFCHR:
printf("character device\n");
break;
case S_IFDIR:
printf("directory\n");
break;
case S_IFIFO:
printf("FIFO/pipe\n");
break;
case S_IFLNK:
printf("symlink\n");
break;
case S_IFREG:
printf("regular file\n");
break;
case S_IFSOCK:
printf("socket\n");
break;
default:
printf("unknown?\n");
break;
}
// 如何查看stat 的demo
// man 2 stat
// 输入G
return 0;
}
就是Linux 的 stat命令
4.1.1.文件理论:
inode 本质就是一个结构体, 存储了文件属性信息, 权限、 类型、大小、 时间 、 用户
每一个文件都有(dentry[文件向导]) 用于保存文件名和文件inode节点号
里面保存 文件名称和inode号
可以通过inode号去寻找 inode结构体,里面保存文件的基本信息和文件的位置通过文件位置找到文件磁盘位置,
文件硬链接: 2个文件有不同的文件名称,相同的inode 号保存在 dentry中
删除文件:把dentry和inode删除,文件还在磁盘上,重新拷贝文件就是覆盖
数据恢复:恢复dentry和inode即可寻找对应关系
文件类型16位:
前9位文件权限
3位特殊权限
4位文件类型, 如下图:
4.2. 重定向:
int main(int argc, const char *argv[])
{
// int fd;
// fd = open("test.temp", O_RDWR | O_CREAT| O_APPEND , S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
// if(-1 == fd)
// {
// perror("open failed1");
// return -1;
// }
// char* ch="abc";
// write(fd,ch,strlen(ch));
// close(fd);
// //remove("test.temp");
// char ch1[1024];
// scanf("%s",ch1);
// unlink("test.temp");
int fd1 = open("a.txt", O_RDWR| O_TRUNC );
int fd2 = open("b.txt", O_RDWR| O_TRUNC );
int fdret = dup2(fd1,fd2); // fd2指向fd1,那么 fd2输出内容也写入fd1
// 返回文件描述符 fd2
printf("fd1--%d--fd2--%d---fdret---%d\n",fd1,fd2,fdret);
write(fdret,"fdret",5);
int ret=write(fd2,"fd2",3);
printf("ret---%d",ret); //写入到a.txt
dup2(fd1,STDOUT_FILENO); // 重定向STDOUT_FILENO,输入内容写入到fd1中的a.txt
printf("---------hello------");
return 0;
}
5. 目录
5.1. 使用ls -l 查看目录权限理解,目录也是一个文件,如果对应权限被改了,不能执行对应操作
5.2. opendir、readdir函数
int main006(int argc, const char *argv[])
{
DIR * dir= opendir("..");
if(dir==NULL){
perror("open dir fail");
exit(1);
}
struct dirent *sdp;
// 没有内容了返回NULL
while( (sdp= readdir(dir)) !=NULL ){
if(strcmp(sdp->d_name,".")== 0 || strcmp(sdp->d_name,"..")== 0){
continue;
}
printf("%s\n",sdp->d_name);
}
closedir(dir);
return 0;
}