4-文件IO-从printf开始

本着从使用中学习原则,我不打算讲太多的细节,也不想讲太多有关内核的知识。但是适当的了解一点内核,对学习 linux 编程会非常有帮助。

printf 在干什么

学过C语言的朋友第一天学习的函数恐怕就是 printf("Hello world\n")了,无论是在 Windows下,还是 Linux 下,这个函数都能使用。对我们程序员来说,它的底层实现是透明的。而 printf ,就是所谓的 C 语言库函数,它依赖于操作系统。

在 linux 中,printf 的实现,实际上间接的调用了 linux 的系统调用接口,这类似于 windows api。那么 printf 调用了 linux 的哪个系统调用呢?我们来扒一扒源码(linux 0.11,为什么用这么老的版本,因为我们都是初学者,完不起太高大上的几百万行的linux 2.X)。

// 文件:init/main.c
static int printf(const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    write(1,printbuf,i=vsprintf(printbuf, fmt, args)); // 重点在这里
    va_end(args);
    return i;
}

短短几行代码,把 write 给暴露出来了,也就是说,真正干活的,就是 linux 的系统调用 write 函数。我们不需要关心太多细节,知道 printf 调用了 linux 的系统调用接口 write 就够了。

实际上,很多 C 语言库比如 fwrite, fputs 等等这些写文件的函数,最终也是调用了 write 这个函数,区别就在于 write 的第一个参数,这是一个整型数,它标记了你的数据要写到哪去。

再深入一点,想看看 write 函数长啥样?

对于大多数朋友,后面可以不用再看了,如果你感兴趣,可以继续往下读。

在 linux 0.11 中,write 本质上是一个宏来制作的函数。

#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
    long __res; \
    __asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
    if (__res>=0) \
        return (type) __res; \
    errno=-__res; \
    return -1; \
}

_syscall3(int,write,int,fd,const char *,buf,off_t,count)

上面代码看着有点恶心,没关系,我们来简化一下。看下面这个简化版本,最终形成的函数是这样。

int write(int fd, const char *buf, off_t, count) {
    long __res;
    __asm {
        //... 参数传递,这里省略
        mov eax, __NR_write // __NR_write的值定义为 4
        int 0x80 // 这是重点,32位陷阱门
        //... 返回值处理,这里省略
    }
    return __res;
 }

有些同学不禁要问,你在写啥呢?为了简化理解,你把重点放在下面两行足以。

mov eax, 4
int 0x80

这两句你可以简单理解成,他调用了另外一个函数,这个函数是真正的内核函数,名为 sys_write,为什么是这个函数而不是别的,实际是由那个数字 4 决定的,不同的数字,会调用不同的内核函数,如果那个数字是 3,那将调用 sys_read 这个内核函数。

总结

printf 的调用链,实际上就是长成这样:

Created with Raphaël 2.1.0 printf write int 0x80 sys_write

printf 和 write 属于用户层函数,int 0x80 相当于一扇门,通过这扇门,进入到了内核函数 sys_write 。sys_write 完成后,再一层一层的将结果返回到 printf。

为什么要通过“门”才能调用 sys_write 而不能直接调用 sys_write ? 这个问题可不简单,你需要系统学习一下 CPU 的保护模式了。如果你对上面的 int 0x80 这个“门”很感兴趣,请参考我的另一系列博文,《OS学习笔记》,这里将有很详细的介绍。

不过提醒一下,千万别跑偏了,我们现在只是在学习 Linux 环境编程,不要在内核上走的太远。

到这里,我想应该够了。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值