Linux系统编程-文件-系统调用

113 阅读8分钟

你只要记住个系统调用的名字,然后到man手册的第2卷去查就行

查看umask

在终端输入umask,返回八进制的umask

错误处理函数

系统会设置一个默认的errno全局变量表示当前发生的错误,但是这个数字我们看不懂,用错误处理函数char* strerror(int errnum)进行翻译。
例如:printf("xxx error: %s\n",strerror(errno));

open

两种参数填入方法,返回文件描述符,文件描述符为-1表示失败

  1. int open(const char* pathname,int flags);
  2. 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号

image.png

传入和传出参数

反正就是用指针直接改变本体那一套

传入参数 const &

  1. 指针作为函数参数
  2. 通常有const关键字修饰
  3. 指针指向有效区域,在函数内部做读操作

传出参数 用指针接返回值

  1. 指针作为函数参数
  2. 在函数调用之前,指针指向的空间无意义,但是必须有效
  3. 在函数内部,做写操作
  4. 函数调用结束后,充当函数返回值

传入传出参数 改变本体

  1. 指针作为函数参数
  2. 在函数调用之前,指针指向的空间有意义
  3. 在函数内部,先读后写
  4. 函数调用结束后,充当函数返回值

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;

}