write函数过程解析 __write_nocancel

本文详细解析了write函数的工作原理,从用户空间调用开始,逐步深入到内核空间的处理流程,包括汇编代码示例及系统调用表的查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

write函数作为用户向终端或者文件进行写数据的重要函数,有着重要的作用。

|------|          |---------|      |---------|     |----------| 
| write |----->|sys_write|-------->|vfs_write|------->|ext4_write|
|------|          |---------|             |---------|             |----------|

     上面是write函数从用户空间到内核空间的过程。比如下面这个具体的例子write_test.c:

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char buffer[10] = "hello\n";
    int count;
    int fd = open ("ab.txt",O_RDWR);
    if (fd == -1)
    {
        fprintf(stderr,"can't open file:[%s]\n","ab.txt");
        exit(EXIT_FAILURE);
    }
    count = write(fd,buffer,strlen(buffer));
    if (count == -1)
    {
        fprintf(stderr,"write error\n");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

采用gcc静态编译的方式:gcc -static -o read_write  read_write.c  【使用静态编译的方式是为了更好的观看汇编代码】

接下来进行如下:root@node1:~/unixprogram# objdump  -d write_test > assemble   【注:因显示的汇编代码有11万行,所以保存为文件,采用动态链接库的方式文件小很多,这是默认的gcc编译选项】

查看assemble文件:【挑选重要的行】

08048250 <main>:
......
80482d9:    89 44 24 04              mov    %eax,0x4(%esp)
80482dd:    8b 44 24 18              mov    0x18(%esp),%eax
80482e1:    89 04 24                 mov    %eax,(%esp)
80482e4:    e8 e7 77 00 00           call   804fad0 <__libc_write>
......

上面是对应于main函数中的汇编,显示的是调用的是__libc_write函数

0804fad0 <__libc_write>:
 804fad0:    65 83 3d 0c 00 00 00     cmpl   $0x0,%gs:0xc
 804fad7:    00
 804fad8:    75 21                    jne    804fafb <__write_nocancel+0x21>

上面对应的是__libc_write函数的汇编代码,在__libc_write函数中显示它调用了__write_nocancel函数

0804fada <__write_nocancel>:
 804fada:    53                       push   %ebx
 804fadb:    8b 54 24 10              mov    0x10(%esp),%edx
 804fadf:    8b 4c 24 0c              mov    0xc(%esp),%ecx
 804fae3:    8b 5c 24 08              mov    0x8(%esp),%ebx
 804fae7:    b8 04 00 00 00           mov    $0x4,%eax
 804faec:    cd 80                    int    $0x80
 ......

从__write_nocancel函数中我们可以看到,在这里传递给eax寄存器立即数4,这是系统调用write的调用number,然后就进行软中断int $0x80进入到内核状态,进行的是system_call()中断处理函数。

【补充说明】在glibc-2.11.2中只有__libc_write函数原型,而对于__write_nocancel是根据系统调用模板生成的,在源码中没有,其中__libc_write在glibc-2.11.2中的io/write.c中有定义。如下

     /* Write NBYTES of BUF to FD.  Return the number written, or -1.  */
ssize_t
  __libc_write (int fd, const void *buf, size_t nbytes)
  {
    if (nbytes == 0)
        return 0;
    if (fd < 0)
      {
        __set_errno (EBADF);
        return -1;
      }
    if (buf == NULL)
      {
        __set_errno (EINVAL);
        return -1;
      }

    __set_errno (ENOSYS);
    return -1;
  }
  libc_hidden_def (__libc_write)
  stub_warning (write)

  weak_alias (__libc_write, __write)
  libc_hidden_weak (__write)
  weak_alias (__libc_write, write)
  #include <stub-tag.h>

  而系统调用部分是根据如下几个文件生成:glibc-2.11.2/sysdeps/unix/syscall.S  glibc-2.11.2/sysdeps/unix/syscalls.list  glibc-2.11.2/sysdeps/unix/syscall-template.S

对于系统调用处理的汇编代码位于linux源码中的 arch/x86/kernel/entry_32.S 【注:内核版本为2.6.39 处理器:x86中的32位】

ENTRY(system_call)
    RING0_INT_FRAME            # can't unwind into user space anyway
    pushl_cfi %eax            # save orig_eax
    SAVE_ALL
    GET_THREAD_INFO(%ebp)
                    # system call tracing in operation / emulation
    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
    jnz syscall_trace_entry
    cmpl $(nr_syscalls), %eax
    jae syscall_badsys
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax,PT_EAX(%esp)        # store the return value
......


这是系统调用的处理函数,通过找到系统调用表中的write系统调用,然后进入到sys_write。

/*
 * This file contains the system call numbers.
 */

#define __NR_restart_syscall      0
#define __NR_exit          1
#define __NR_fork          2
#define __NR_read          3
#define __NR_write          4
#define __NR_open          5
#define __NR_close          6
#define __NR_waitpid          7
#define __NR_creat          8
........

上面的是linux下的系统调用号列表,位于linux-2.6.39/arch/x86/include/asm/unistd_32.h中。

而对于系统调用表中的write函数位于linux-2.6.39/fs/read_write.c中,如下:

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
        size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;

    file = fget_light(fd, &fput_needed);
    if (file) {
        loff_t pos = file_pos_read(file);
        ret = vfs_write(file, buf, count, &pos);
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }

    return ret;
}

<think>嗯,用户问的是write函数和kernel_write函数的区别。我得先回忆一下这两个函数的作用和所在的上下文。首先,用户可能是在学习Linux系统编程或者内核开发,所以需要区分用户空间和内核空间的函数write函数应该是用户空间的标准I/O函数,属于系统调用接口,用来向文件描述符写入数据。而kernel_write可能是内核内部的函数,用于在内核模块中执行写操作,不需要经过系统调用。 不过,我得确认一下kernel_write的具体情况。记得在内核中,通常会有一些辅助函数,比如kernel_read和kernel_write,它们被设计成供内核其他部分调用,处理内核空间的数据。这些函数可能不需要切换到用户上下文,直接操作文件结构。 然后,用户可能想知道它们的区别,包括使用场景、参数差异、安全性和上下文要求。例如,write在用户空间需要文件描述符,而kernel_write在内核中可能直接使用file结构体指针。此外,kernel_write可能不需要检查用户空间的缓冲区,因为调用者已经在内核里了。 还要注意权限和阻塞的问题。用户空间的write可能会被信号中断,而内核中的kernel_write可能以不同的方式处理阻塞。另外,错误处理也可能不同,内核函数可能返回不同的错误码。 需要举一个例子说明,比如在内核模块中如何使用kernel_write,而普通程序用write。还要提醒用户注意,直接使用内核函数的风险,因为它们仅供内核开发,普通开发者应该用系统调用。 最后,总结两者的关键区别,强调上下文环境、参数、安全机制和用途的不同。确保回答清晰,覆盖用户可能关心的各个方面,帮助他们理解为什么存在这两个函数以及何时使用它们。</think>在 Linux 系统开发中,`write` 和 `kernel_write` 是两个不同层次的函数,主要区别体现在 **调用上下文**、**使用场景** 和 **实现细节** 上。以下是它们的核心区别: --- ### **1. write 函数** - **所属层次**:用户空间(User Space)的标准系统调用接口。 - **作用**:将数据从用户空间的缓冲区写入文件描述符(如文件、套接字等)。 - **特点**: - 通过 `glibc` 封装,最终触发 `sys_write` 系统调用进入内核。 - 需要用户态和内核态的上下文切换(性能开销)。 - 参数为文件描述符 `fd`、用户空间缓冲区指针 `buf` 和大小 `count`。 - 安全性:内核会检查用户提供的缓冲区是否合法(防止非法内存访问)。 - **示例**: ```c #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); ``` --- ### **2. kernel_write 函数** - **所属层次**:内核空间(Kernel Space)的内部函数。 - **作用**:在内核中直接向文件写入数据,无需经过系统调用流程。 - **特点**: - 仅供内核模块或驱动使用(如文件系统、设备驱动)。 - **无需上下文切换**,直接操作内核数据结构(如 `struct file`)。 - 参数为内核中的 `struct file *`、内核空间缓冲区指针 `buf` 和大小 `count`。 - 安全性:假设调用者已在内核态,缓冲区必须由内核分配或验证。 - 可能绕过某些权限检查(需谨慎使用)。 - **原型**(Linux 内核源码): ```c ssize_t kernel_write(struct file *file, const void *buf, size_t count, loff_t *pos); ``` --- ### **关键区别总结** | **特性** | **write** | **kernel_write** | |----------------------|-------------------------------|---------------------------------| | 调用者上下文 | 用户空间程序 | 内核模块/驱动 | | 缓冲区来源 | 用户空间(需拷贝到内核) | 内核空间(直接操作) | | 权限检查 | 完整(用户权限、文件描述符) | 可能简化(依赖调用者保障) | | 性能 | 有用户态/内核态切换开销 | 直接操作,无切换开销 | | 典型使用场景 | 应用程序写入文件 | 内核记录日志、修改文件元数据等 | --- ### **示例场景** - **用户程序**:用 `write` 向文件写入数据。 - **内核模块**:用 `kernel_write` 在内核中直接修改文件(例如记录内核日志到文件)。 --- ### **注意事项** - `kernel_write` 是内核内部 API,不同内核版本可能变化,需谨慎使用。 - 普通开发者只需关注 `write`,内核开发者才会涉及 `kernel_write`。 如果需要更深入的实现细节,可以参考 Linux 内核源码中的 `fs/read_write.c`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值