【linux】linux系统调用及文件IO操作

一、系统调用

1、概述

系统调用: 就是操作系统内核 提供给用户可以操作内核 一组函数接口。用户 借助 系统调用 操作内核。比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。

进程的空间分为:内核空间 和 用户空间。系统调用是属于操作系统内核的一部分,运行在内核态下,通过软件中断切换到内核态。

2、系统调用和库函数区别

系统调用 是内核提供的一组函数接口。

库函数 是第三方(用户提供)的函数接口。
如果库函数没有调用系统调用该库函数不能操作内核。比如:字符串操作函数strcpy, bzero

如果库函数 调用 系统调用该库函数能操作内核。比如:fopen fclose fwrite fget

3、系统调用及IO操作

系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。 文件IO中缓冲区刷新机制需要内核管理,库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用IO系统调用的次数,提高了访问效率。

二、文件描述符

1、概述

在 Linux的世界里一切设备皆文件,我们可以系统调用I/O 的函 数(I:input,输入;0:output..输出),对文件进行相应的操作 ( open()、close()、write()、read()等。

打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

Linux将系统调用 打开或新建的文件 用非负整数 来表示。而这个非负整数 就是文件描述 符。

系统会为每一个进程文件 分配一个文件描述符表,管理该进程的所有文件描述符。

系统会为 每一个进程文件 打开三个文件描述符:0,1,2

  • 0:标准输入设备(键盘) scanf
  • 1:标准输出设备(终端)printf
  • 2:标准错误输出 (终端)perror

2、文件描述符表对文件描述符的管理

文件描述符表 是通过 "位图 " 来管理文件描述符。使用**1024位(0-1023)**二进制位管理,位数代表 的就是文件描述符,位上的值1表示打开,值0表示关闭,前三位(0-2)为标准文件描述符。打开文件的时候默认选择最小可用的文件描述符(3)给打开的文件用。

3、查看当前系统文件描述符最大数量ulimit -a

查看命令:ulimit -a

修改命令:ulimit -n 2048

三、文件IO的操作

文件常用操作IO:open close read write

1、打开文件 open

头文件:

#include<sys/type.h>

#include<sys/stat.h>

#include <funtl.h>

相关函数:

函数功能: 打开文件,如果文件不存在则可以选择创建。

//二参数open用于 打开已存在的文件

int open(const char *pathname, int flags);

//三参数open用于 打开不存在的文件 mode是文件在磁盘上的权限

int open(const char *pathname, int flags, mode_t mode);

//返回值:成功返回最小文件描述符,失败返回-1

参数:

  • pathname:文件的路径及文件名
  • flags:打开文件的行为标志,必选项 O_RDONLY(以只读方式打开, O_WRONLY(以只写方式打开), O_RDWR(以可读可写方式打开)
  • mode:这个参数,只有在文件不存在时有效 ,指新建文件时指定文件的磁盘权限

flags 文件的操作权限可选项,和必选项按位或起来:

|------------|-------------------------------------------------|
| 取值 | 含义 |
| O_CREAT | 文件不存在则创建文件,文件存在则先删除再重新创建文件,使用此选项时需使用mode说明文件的权限 |
| O_EXCL | 如果同时指定了O_CREAT,且文件已经存在,则出错(防止已有文件被删除) |
| O_TRUNC | 如果文件存在,则清空文件内容 |
| O_APPEND | 写文件时,数据添加到文件末尾 |
| O_NONBLOCK | 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O |

mode文件在磁盘的用户权限:

磁盘文件的用户权限分类:所有拥有者权限(u)、同组用户权限(g)、其他用户权限(o)

权限分为:读(4)、写(2)、执行(1),可以相互组合:

7--->可读可写可执行 6--->可读可写 5--->可读可执行

4--->只读 3--->可写可执行 2--->只写 1--->可执行

mode的权限表示0xxx 每一个x都是4、2、1的组合,如:

前第一个0代表其为八进制数:

0777 所有者、同组用户、其他用户都是可读可写可执行

0666 所有者、同组用户、其他用户都是可读可写

查看mode的系统权限掩码 umask:

查看掩码命令:umask

文件的最终权限=给定的权限异或掩码( & ~umask)

umask mode:设置掩码,mode为八进制数

umask -S:查看各组用户的默认操作权限

2、close 关闭文件描述符

头文件:#include<unistd.h>

函数:int close(int fd);

功能: 关闭已打开的文件

参数: fd : 文件描述符,open()的返回值

返回值: 成功:0 失败: -1并存放在全局变量errno中

注意:close工作步骤,先将文件描述符的数量-1,当文件描述符的数量变为0的时候 ,系统回收文件描述符所占的内核空间。

3、向文件写数据write

头文件:#include<unistd.h>

函数:size_t write(int fd, const void *buf, size_t count);

功能: 把指定数目的数据写到文件fd中

参数:

fd : 文件描述符

buf : 数据首地址

count : 写入数据的长度(字节)

返回值: 成功:实际写入数据的字节个数 失败: - 1

4、read读取文件数据

#include<unistd.h>

size_t read(int fd, void *buf, size_t count);

功能: 把指定数目的数据读到内存(缓冲区)

参数:

fd : 文件描述符

buf : 内存首地址

count : 读取的字节个数

返回值: 成功:实际读取到的字节个数,读完文件数据返回0 失败: - 1

5、综合案例:实现cp(copy)命令

举例:cp b.txt test 将b.txt文件拷贝到test目录中

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
int main(int argc,char *argv[])//argc为传参个数,指针数组argv存放每个参数的内容
{
    //判断参数是否正确(./a.out a.txt test)
    //若正确,argc=3,argv[0]=./a.out,argv[1]=a.txt,argy[2]=test
    if(argc!=3)
    {
        printf("缺少参数");
        return 0;
    }
    //以只读的方式 打开a.txt文件
    int fp_r=open(argv[1],O_RDONLY);
    if(fp_r<0)
    {
        perror("open");
        return 0;
    }
    //以写的方式 在test目录中打开a.txt
    char filename[32]="";
    sprintf(filename,"%s/%s",argv[2],argv[1]);
    int fp_w=read(filename,O_WRONLY|O_CREAT,0666);
    if(fp_w<0)
    {
        perror("open");
        return 0;
    }

    //不同的从fd_r中读取文件数据 写入fd_w文件中
    while(1)
    {
    unsigned char buf[128]="";
    int len=read(fp_r,buf,sizeof(buf));
    if(len<=0)
        break;
    printf("读取的数据大小为%d",len);
    write(fp_w,buf,sizeof(buf));
    }
    //关闭文件
    close(fp_r);
    close(fp_w);
    return 0;
}

四、文件的阻塞特性

对于一些设备文件读写操作,如管道,套接字,标准设备文件,默认缓冲区没有数据读会带阻塞,默认缓存区满的状态,写也会阻塞。

文件描述符决定阻塞和非阻塞,而不是read write函数。文件描述符默认为阻塞的。

1、open打开文件 默认为阻塞特性

文件描述符 事先不存在用open。如果从终端输入的数据没有换行符,默认调用read读终端设备就会阻塞。

2、 open打开文件 设置为非阻塞特性

3、通过fcntl设置文件的阻塞特性

文件描述符事先存在用funtl。 功能:改变已打开的文件性质,fcntl针对描述符提供控制。

头文件:

#include<unistd.h>

#include<fcntl.h>

函数:

int fcntl(int fd, int cmd, ... /* arg */ );

参数:

fd:操作的文件描述符

cmd:操作方式

arg:针对cmd的值,fcntl能够接受第三个参数int arg。

返回值: 成功:返回某个其他值 失败:-1

fcntl函数有5种cmd功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

设置一个存在的文件描述符的阻塞特性的步骤

  1. fcntl获取文件描述符的状态标记
  2. 修改 获取到的 文件描述符的状态标记
  3. 将修改后的状态标记 使用fcntl设置到文件描述符中

五、获取文件的状态信息

头文件:

#include<sys/type.h>

#include<sys/stat.h>

#include<unistd.h>

函数:

int stat(const char *path, struct stat *buf);

int lstat(const char *pathname, struct stat *buf);

功能: 获取文件状态信息

stat和lstat的区别: 当文件是一个符号链接时,lstat返回的是该符号链接本身的信息; 而stat返回的是该链接指向的文件的信息。

参数:

path/pathname:文件名

buf:保存文件信息的结构体

返回值: 成功: 0 失败: -1

struct stat结构体说明:

struct stat {
    dev_t st_dev; //文件的设备编号
    ino_t st_ino; //节点
    mode_t st_mode; //文件的类型和存取的权限
    nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
    uid_t st_uid; //用户ID
    gid_t st_gid; //组ID
    dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设
备编号
    off_t st_size; //文件字节数(文件大小)
    blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
    blkcnt_t st_blocks; //块数
    time_t st_atime; //最后一次访问时间
    time_t st_mtime; //最后一次修改时间
    time_t st_ctime; //最后一次改变时间(指属性)
};

st_mode(16位整数)参数说明 :

由于上述形式的测试是普通的,因此POSIX定义了其他宏,以便更简洁地编写st_mode中的文件类型测试:

|-------------------|---------------------------|
| S_ISREG(st_mode) | 是否是普通文件(regular file) |
| S_ISDIR(st_mode) | 是否是目录文件(directory) |
| S_ISCHR(st_mode) | 是否是字符设备(character device) |
| S_ISBLK(st_mode) | 块设备(block device) |
| S_ISFIFO(st_mode) | 管道 |
| S_ISLNK(st_mode) | 符号链接(软连接) |

st_mode控件的文件模式组件定义了下列掩码值:

六、文件目录操作

读取一个目录下的所有文件名

1、打开目录(得到文件目录的句柄)

头文件:

#include<sys/types.h>

#include<dirent.h>

函数:

DIR *opendir(const char *name);
功能:打开一个目录

参数name:目录名

返回值: 成功:返回指向该目录结构体指针 失败:NULL

2、读取目录

#include<dirent.h>

struct dirent *readdir(DIR *dirp);
功能:读取目录 调用一次只能读取一个文件

参数 dirp:opendir的返回值

返回值: 成功:目录结构体指针 失败:NULL

结构体dirent说明:

struct dirent
{
    ino_t d_ino; // 此目录进入点的inode
    off_t d_off; // 目录文件开头至此目录进入点的位移
    signed short int d_reclen; // d_name 的内容长度, 不包含NULL 字符
    unsigned char d_type; // d_type 所指的文件类型
    char d_name[256]; // 文件名
};

d_type相关数据:

3、关闭目录

头文件:

#include<sys/types.h>

#include<dirent.h>

函数:

int closedir(DIR *dirp);
功能:关闭目录

参数dirp:opendir返回的指针

返回值: 成功:0 失败:-1

相关推荐
m0_694938012 小时前
Leetcode打卡:字符串及其反转中是否存在同一子字符串
linux·服务器·leetcode
飞的肖3 小时前
从测试服务器手动热部署到生产环境的实现
java·服务器·系统架构
看星星的派大星3 小时前
rk3588 android12 root
linux
飘飘燃雪3 小时前
Linux Modbus协议详解,代码示例
linux·运维·服务器·modbus
蜗牛hb3 小时前
Kali基础知识
linux·运维·服务器
tingting01194 小时前
docker 释放磁盘空间--常用清理命令
运维·docker·容器
乐维_lwops4 小时前
安全筑堤,效率破浪 | 统一运维管理平台下的免密登录应用解析
运维·服务器·安全
云飞云共享云桌面4 小时前
如何让企业研发设计团队低配电脑流畅做3D大装配设计?
服务器·3d·电脑
恩爸编程4 小时前
深入浅出 Linux 操作系统
linux·运维·服务器·linux系统介绍·linux操作系统介绍·linux操作系统是什么·linux操作是什么
明达技术5 小时前
分布式 I/O 配合高冗余 PLC,打造高效控制新典范
运维·分布式