Linux文件IO编程

文件介绍

linux文件结构

在 linux 中,内核会根据不同的设备类型,封装出对应的不同的文件类型,以便顶层的应用
进程快速识别底层中的设备,主要分为以下几种:
d : 目录文件

  • : 普通文件
    p : 管道文件
    l : 链接文件
    s : 套接字文件
    c : 字符型设备文件
    b : 存储块型设备文
文件描述符:文件描述符是一个简单的整数,它是一个索引值,并指向内核中每个进程打开文件的记录表。

当顶层的应用进程需要对底层的设备进行操作时,需要通过内核将设备对应的文件打开,同时内核会将打开的文件封装成文件描述符返回给用户进程。
如果应用进程需要对对应的文件进行读写操作时,就会将文件描述符作为参传递给内核函数,进行操作。

  1. 描述符是相对一个进程来说,每个进程都可以维护管理众多个描述符,即相当于可以管
    理众多打开的文件
  2. 多个进程间的描述是独立的
  3. 进程创建后系统会自动给自动创建三个描述符,分别为 0,1,2
  4. 进程退出后,描述符会被系统自动回收
一般情况下,当启动一个进程时,内核会自动为这个进程打开三个文件

分别为:
标准输入(键盘),对应的文件描述符为 0,即宏 stdin
标准输出(屏幕),对应的文件描述符为 1,即宏 stdout
标准出错处理(屏幕),对应的文件描述符为 2,即宏 stder

如果后面进程需要操作新的设备或者文件,则需要通过内核提供的功能函数先打开文件并返回文件描述符,才能进行各种功能操作,
而这个文件描述总是从 3 开始递增分配,一个进程最多只能打开 1024 个

先查看 LINUX 默认的文件描述符:

ulimit -n

1024
我们用命令
ulimit -HSn
65536

系统调用

用户的应用进程可以调用内核的功能函数对其封装的文件(设备)进行操作。但是实际上这个调用过程并不是直接进行的,
而是通过另外一个封装接口来间接操作,这个封装好的接口就是内核提供给应用程序使用的 “系统调用”

所谓的系统调用其实就是操作系统提供给用户程序调用的一组“特殊的接口”,用户程序可以通过这个特殊的接口获得操作系统内核提供的服务。
在这里插入图片描述
用户空间是应用程序启动后执行时所处的地址空间

内核空间是操作系统及其服务执行时所处的地址空间。

它们分别运行在不同级别上,在逻辑上相互隔离的。通常情况下,用户进程不允许访问内核数据,也就不能访问内核函数,它们只能操作用户数据,调用用户函数
但是,在有些情况下,用户空间进程需要获得一定的系统服务(调用内核的函数),这时操作系统需要提供一组封装好的“特殊接口”,
以便用户进程通过这组接口,进入内核空间访问内核函数,这时程序运行空间需要从用户空间进入内核空间,处理完之后再返回用户空间。

linux 系统下,应用程序可以直接调用内核的系统调用接口,需要使用以下接口:
int syscall(int number, …)
number 是需要的函数的系统调用号

系统调用几种常用方法:
  1. 通过 syscall(调用号, xxxxx)直接进行系统调用,在 sys/syscall.h 中有所有可能的系统调用号的宏定义。
    在这里插入图片描述

例如,通过syscall函数使用系统调用号执行chmod改变文件权限

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        int rc;
        rc = syscall(SYS_chmod, "/etc/passwd", 0444);

        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod succeess!\n");
        return 0;
}

在 linux 操作中,特别是输入输出操作中,直接使用系统调用的效率非常低。
主要是
1)系统开销大
因为在执行系统调用的时,Linux 必须从用户态转换到内核态,还要返回用户态。
2)硬件对底层系统调用一次所能读写的数据块做出的限制

  1. 通过glibc提供的库函数进行间接系统调用
    这个用户编程接口即应用空间实的功能函数封装库,例如 libc 库中,就实现了输入输出操作
    中的 open,read,write 等常用的库函数。通过这些封装好的库函数,我们可以更加快速、便
    捷地使用内核提供的系统调用

在这里插入图片描述
应用进程在调用系统调用之前,通过调用一组已经实现的用户编程接口程序,对数据及操作进行加工处理
再调用内核提供的系统调用,可以在某种程序上避免出现以上问题。
例如,通过库函数使用chmod函数改变文件权限

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

int main()
{
        int rc;

        rc = chmod("/etc/passwd", 0444);
        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod success!\n");
        return 0;
}

3、通过int指令(int 0x80)陷入内核态
为什么使用int 0x80能调用那么多系统调用?
系统调用与0x80号中断绑定。当使用系统调用时,就触发int 0x80,中断处理函数就通过eax寄存器获取要使用的系统调用号。
这样做是因为系统调用数量太多,如果都有一个中断号的话,中断号不够用,所以用一个0x80集中管理。
操作系统中有一个表,用来保存各个系统调用函数的地址。这个表是一个数组,所以通过下标就可以访问到不同的函数地址。
故可以做到一个中断号+不同系统调用号 就管理多个系统调用

#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        long rc;
        char *file_name = "/etc/passwd";
        unsigned short mode = 0444;

        asm(
                "int $0x80"
                : "=a" (rc)
                : "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
        );

        if ((unsigned long)rc >= (unsigned long)-132) {
                errno = -rc;
                rc = -1;
        }

        if (rc == -1)
                fprintf(stderr, "chmode failed, errno = %d\n", errno);
        else
                printf("success!\n");

        return 0;
}

如果eax寄存器存放的返回值(存放在变量rc中)在-1和-132之间,就必须要解释为出错码。在/usr/include/asm-generic/errno.h文件中定义最大出错码为132,
这时,将错误码写入errno,置系统调用返回值为-1,否则返回的是eax寄存器中的值

此程序在32位系统下,可以正常执行chmod;但在64位系统下,则显示chmode failed, errno = 22:即参数无效

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值