【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

相关推荐
zym大哥大5 分钟前
Linux的权限
linux·服务器
Stark-C8 分钟前
功能齐全,支持协作 | Docker部署一款支持多人共享的私密浏览器『n.eko』
运维·docker·容器
嘟嘟Listing28 分钟前
设置jenkins时区记录
运维·jenkins
嘟嘟Listing29 分钟前
jenkins docker记录
java·运维·jenkins
伴野星辰30 分钟前
小乌龟TortoiseGit 安装和语言包选择
linux·运维·服务器
枫叶丹436 分钟前
【在Linux世界中追寻伟大的One Piece】多线程(一)
java·linux·运维
残念ing38 分钟前
【Linux】—简单实现一个shell(myshell)
linux·运维·服务器
明月心9521 小时前
linux mount nfs开机自动挂载远程目录
linux·运维·服务器
Ray55051 小时前
bridge-multicast-igmpsnooping
linux·服务器·网络
库库的里昂1 小时前
Linux系统Docker部署开源在线协作笔记Trilium Notes与远程访问详细教程
linux·运维·docker·开源