linux下的系统调用函数到内核函数的追踪

使用的 glibc : glibc-2.17  使用的 linux kernel :linux-3.2.07

系统调用是内核向用户进程提供服务的唯一方法,应用程序调用操作系统提供的功能模块(函数)。
用户程序通过系统调用从用户态(user mode)切换到核心态(kernel mode ),从而可以访问相应的资源。这样做的好处是:
为用户空间提供了一种硬件的抽象接口,使编程更加容易。
有利于系统安全。
有利于每个进程度运行在虚拟系统中,接口统一有利于移植。


             运行模式、地址空间、上下文

运行模式(mode)
Linux 使用了其中的两个:特权级0和特权级3 ,即内核模式(kernel mode) 和用户模式(user mode )
地址空间(space )
a)每个进程的虚拟地址空间可以划分为两个部分:用户空间和内核空间
b)在用户态下只能访问用户空间;而在核心态下,既可以访问用户空间,又可以访问内核空间。 
c)内核空间在每个进程的虚拟地址空间中都是固定的(虚拟地址为3G~4G的地址空间)。
上下文(context )
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
a)用户级上下文:正文、数据、用户栈以及共享存储区;
b)寄存器上下文:通用寄存器、程序寄存器(IP )、处理机状态寄存器(EFLAGS)、栈指针(ESP);
c)系统级上下文:进程控制块task_struct 、内存管理信息(mm_struct 、vm_area_struct、pgd 、pmd、pte 等)、核心栈等。

             系统调用、API和C 库
a)Linux 的应用编程接口(API)遵循POSIX标准
b)Linux 的系统调用作为c库的一部分提供。c库中实现了Linux 的主要API,包括标准c库函数和系统调用。
c)应用编程接口(API)其实是一组函数定义,这些函数说明了如何获得一个给定的服务;而系统调用是通过软中断向内核发出一个明确的请求,每个系统调用对应一个封装例程(wrapper routine,唯一目的就是发布系统调用)。一些API应用了封装例程。API还包含各种编程接口,如:C库函数、OpenGL 编程接口等
d)系统调用的实现是在内核完成的,而用户态的函数是在函数库中实现的

           系统调用与操作系统命令
a)操作系统命令相对应用编程接口更高一层,每个操作系统命令都是一个可执行程序,比如ls 、hostname 等,
b)操作系统命令的实现调用了系统调用
c)通过 strace 命令可以查看操作系统命令所调用的系统调用,如:
   strace ls
   strace hostname

            系统调用与内核函数
a)内核函数在形式上与普通函数一样,但它是在内核实现的,需要满足一些内核编程的要求
b)系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现的
c)进入内核后,不同的系统调用会找到各自对应的内核函数,这些内核函数被称为系统调用的“服务例程 ”

系统调用处理程序及服务例程
a)当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
b)系统调用处理程序执行下列操作:
   @[email protected] 在内核栈保存大多数寄存器的内容
   @[email protected] 调用名为系统调用服务例程(system call service routine)的相应的C函数来处理系统调用
   @[email protected] 通过ret_from_sys_call(  ) 函数从系统调用返回

            系统调用流程


          系统调用中参数传递
a)每个系统调用至少有一个参数,即通过 eax 寄存器传递来的系统调用号
b)用寄存器传递参数必须满足两个条件: 
   @[email protected] 每个参数的长度不能超过寄存器的长度
   @[email protected] 参数的个数不能超过6 个(包括eax 中传递的系统调用号),否则,
       用一个单独的寄存器指向进程地址空间中这些参数值所在的一个内存区
c)在少数情况下,系统调用不使用任何参数
d)服务例程的返回值必须写到eax 寄存器

很多系统调用需要不止一个参数
普通C函数的参数传递是通过把参数值写入堆栈(用户态堆栈或内核态堆栈)来实现的。
但因为系统调用是一种特殊函数,它由用户态进入了内核态,所以既不能使用用户态的堆栈
也不能直接使用内核态堆栈

在int $0x80汇编指令之前,系统调用的参数被写入CPU的寄存器。然后,在进入内核态调用系统调用服务例程之前,内核再把存放在CPU寄存器中的参数拷贝到内核态堆栈中。因为毕竟服务例程是C函数,它还是要到堆栈中去寻找参数的

 系统调用小结
程序执行系统调用大致可归结为以下几个步骤:
1、程序调用libc 库的封装函数。
2、调用软中断int 0x80  进入内核。
3、在内核中首先执行system_call 函数(首先将系统调用号(eax)和可以用到的所有CPU寄存器保存到相应的堆栈中(由SAVE_ALL完成),接着根据系统调用号在系统调用表中查找      到对应的系统调用服务例程。
4、执行该服务例程。
5、执行完毕后,转入ret_from_sys_call 例程,从系统调用返回

在深入讨论内核和用户空间库如何实现系统调用的技术细节之前,简要看一下内核以系统调用形式实际提供的各个函数是很有用处的。每个系统调用都通过一个符号常数标识,符号常数的定义是平台相关的,在内核源码中指定,XX表示平台相关,有些是 asm_arch,有的是 asm_generic 

用于实现系统调用的处理程序函数,在形式上有如下几个共同的特性:
1,每个函数的名称前缀都是 sys_ ,将该函数唯一地标识为一个系统调用,更精确的说,标识为一个系统调用的处理程序函数。
2,所有的处理程序函数都最多接收 5 个参数否则,用一个单独的寄存器指向进程  地址空间中这些参数值所在的一个内存区即可。
3,所有的系统调用都在内核态执行

系统调用由内核分配的一个编号唯一标识(系统调用号)。
所有的系统调用都由一处中枢代码处理,根据调用编号和一个静态表,将调用分派到具体的函数。传递的参数也是由中枢代码处理,这样参数的传递独立于实际的系统调用。从用户态到内核态,以及调用分派和参数传递,都是由汇编语言代码实现的。
为容许用户态和内核态之间的切换,用户进程必须通过一条专用的机器指令,引起处理器/内核对该进程的关注,这需要 C 标准库的协助。内核也必须提供一个例程,来满足切换请求并执行相关操作。该例程不能在用户空间中实现,因为其中需要执行普通应用程序不允许执行的命令。

系统调用表 (armV7)

 1 /*
 2 * linux/arch/arm/kernel/calls.S
 3 *
 4 * Copyright (C) 1995-2005 Russell King
 5 *
 6 * This program is free software; you can redistribute it and/or modify
 7 * it under the terms of the GNU General Public License version 2 as
 8 * published by the Free Software Foundation.
 9 *
10 * This file is included thrice in entry-common.S
11 */
12 /* 0 */ CALL(sys_restart_syscall)
13 CALL(sys_exit)
14 CALL(sys_fork_wrapper)
15 CALL(sys_read)
16 CALL(sys_write)
17 /* 5 */ CALL(sys_open)
18 CALL(sys_close)
19 CALL(sys_ni_syscall) /* was sys_waitpid */
20 CALL(sys_creat)
21 CALL(sys_link)
22 /* 10 */ CALL(sys_unlink)
23 CALL(sys_execve_wrapper)
24 CALL(sys_chdir)
25 CALL(OBSOLETE(sys_time)) /* used by libc4 */

以系统调用 open() 函数为例:

1,X86 平台:

1,用户空间
   1,函数 open() 的声明
      @[email protected] 在使用 open()函数时,要 include 

<glibc-2.17\include\fcntl.h>

 1 #ifndef _FCNTL_H
 2 #include
 3 #ifndef _ISOMAC
 4 /* Now define the internal interfaces. */
 5 extern int __open64 (const char *__file, int __oflag, ...);
 6 libc_hidden_proto (__open64)
 7 extern int __libc_open64 (const char *file, int oflag, ...);
 8 extern int __libc_open (const char *file, int oflag, ...);
 9 libc_hidden_proto (__libc_open)
10 extern int __libc_creat (const char *file, mode_t mode);
11 extern int __libc_fcntl (int fd, int cmd, ...);
12 ...

<glibc-2.17\io\fcntl.h>

 1 ...
 2 /* Open FILE and return a new file descriptor for it, or -1 on error.
 3    OFLAG determines the type of access used. If O_CREAT is on OFLAG,
 4    the third argument is taken as a `mode_t‘, the mode of the created file.
 5    This function is a cancellation point and therefore not marked with
 6    __THROW. */
 7 #ifndef __USE_FILE_OFFSET64
 8 extern int open (const char *__file, int __oflag, ...) __nonnull ((1));
 9 #else
10 # ifdef __REDIRECT
11 extern int __REDIRECT (open, (const char *__file, int __oflag, ...), open64)
12      __nonnull ((1));
13 # else
14 # define open open64
15 # endif
16 #endif
17 #ifdef __USE_LARGEFILE64
18 extern int open64 (const char *__file, int __oflag, ...) __nonnull ((1));
19 #endif
20 ...
 1 /* Define a macro which expands inline into the wrapper code for a system
 2    call. */
 3 # undef INLINE_SYSCALL
 4 # define INLINE_SYSCALL(name, nr, args...)  5   ({                                      6     unsigned long int resultvar = INTERNAL_SYSCALL (name, , nr, args);      7     if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0))      8       {                                      9     __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, ));         10     resultvar = (unsigned long int) -1;                 11       }                                     12     (long int) resultvar; })

2,内核空间

(1)系统启动时,对INT 0x80进行一定的初始化。

使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int ,如下图所示。

2)用户程序需要系统提供服务的时候,会通过系统调用产生一个int 0x80的软中断,就会进入到系统调用的入口函数,入口函数存放在以下文件当中

<arch\x86\kernel\entry_32.s>

 1 ENTRY(system_call)
 2     RING0_INT_FRAME # cant unwind into user space anyway
 3     pushl %eax      # save orig_eax ,将系统调用号压入栈中
 4     CFI_ADJUST_CFA_OFFSET 4
 5     SAVE_ALL       #将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,
 6                    #当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,
 7                    #从而实现汇编代码向C程序传递参数
 8
 9     GET_THREAD_INFO(%ebp)
10                   # system call tracing in operation / emulation
11                #GET_THREAD_INFO宏获得当前进程的thread_info结构的地址,获取当前进程的信息。
12                #thread_inof结构中flag字段的_TIF_SYSCALL_TRACE或_TIF_SYSCALL_AUDIT
13                #被置1。如果发生被跟踪的情况则转向相应的处理命令处。
14     testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
15    jnz syscall_trace_entry    #比较结果不为零的时候跳转。
16                               #对用户态进程传递过来的系统调用号的合法性进行检查
17                               #如果不合法则跳到syscall_badsys标记的命令处。
18    cmpl $(nr_syscalls), %eax
19    jae syscall_badsys         #比较结果大于或者等于最大的系统调用号的时候跳转,不合法
20                               #合法则跳转到相应系统调用号所对应的服务例程当中,
21                               #也就是在sys_call_table表中找到了相应的函数入口点。
22            #由于sys_call_table表的表项占4字节字节字节字节,因此获得服务例程指针的具体方法
23            #是将由eax保存的系统调用号乘以4再与sys_call_table表的基址相加。
24 syscall_call:
25     call *sys_call_table(,%eax,4)
26     movl %eax,PT_EAX(%esp)    # store the return value 将保存的结果返回。

<arch\x86\include\asm\ptrace.h>

 1 struct pt_regs {
 2     unsigned long bx;
 3     unsigned long cx;
 4     unsigned long dx;
 5     unsigned long si;
 6     unsigned long di;
 7     unsigned long bp;
 8     unsigned long ax;
 9     unsigned long ds;
10     unsigned long es;
11     unsigned long fs;
12     unsigned long gs;
13     unsigned long orig_ax;
14     unsigned long ip;
15     unsigned long cs;
16     unsigned long flags;
17     unsigned long sp;
18     unsigned long ss;
19 };

接下来,会进入到系统调用表查找到系统调用服务程序的入口函数的地址,再进行跳转,

整个过程如下图所示:

1,系统调用号:

<arch\x86\include\asm\unistd_32.h>

 1 #ifndef _ASM_X86_UNISTD_32_H
 2 #define _ASM_X86_UNISTD_32_H
 3 /*
 4  * This file contains the system call numbers.
 5  */
 6 #define __NR_restart_syscall 0
 7 #define __NR_exit         1
 8 #define __NR_fork         2
 9 #define __NR_read         3
10 #define __NR_write         4
11 #define __NR_open         5
12 #define __NR_close         6
13 #define __NR_waitpid         7

系统调用原型:

<include\linux\syscalls.h>

asmlinkage long sys_open(const char __user *filename,

                                         int flags, int mode);    

其中这里使用了一个宏asmlinkage ,我们再看一下它在系统里的定义:

<arch\x86\include\asm\linkage.h>

#ifdef CONFIG_X86_32
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

后面的 __attribute__((regparm(0)))表示的是不通过寄存器来传递参数,通过栈来传递

所以系统调用的入口函数里面参数的传递:

<arch\x86\kernel\entry_32.s>

ENTRY(system_call)
    SAVE_ALL     #将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,
                 #当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,
                 #从而实现从汇编代码向C程序传递参数。

定义 SAVE_ALL 是将参数压到堆栈中,然后通过堆栈来进行参数的传递

3,获取系统调用入口函数:

<arch\x86\kernel\entry_32.s>

syscall_call:
    call *sys_call_table(,%eax,4)

sys_call_table 每一项占用4个字节。system_call函数可以读取 eax 寄存器,获取当前系统调用的系统调用号,将其乘以 4 生成偏移地址,然后以 sys_call_table 为基址,基址加上偏移地址所指向的内容,既是应该调用的服务程序的地址。

<arch\x86\kernel\syscall_table_32.s>

ENTRY(sys_call_table)
    .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */
    .long sys_exit
    .long ptregs_fork
    .long sys_read
    .long sys_write
    .long sys_open /* 5 */
    .long sys_close
    .long sys_waitpid
    .long sys_creat
    .long sys_link
    .long sys_unlink /* 10 */

在本例中,sys_open 是系统调用服务程序的入口地址

4,调用系统调用函数:(在新的内核中,函数的实现并不是直接通过 sys_xxx 函数,而是通过一个宏的封装)
sys_open -> do_sys_open -> do_filp_open ->do_last-> nameidata_to_filp -> __dentry_open

 1 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
 2 {
 3     long ret;
 4     if (force_o_largefile())
 5         flags |= O_LARGEFILE;
 6     ret = do_sys_open(AT_FDCWD, filename, flags, mode);
 7     /* avoid REGPARM breakage on x86: */
 8     asmlinkage_protect(3, ret, filename, flags, mode);
 9     return ret;
10 }

其中宏 SYSCALL_DEFINE3 定义如下:

<include\linux\syscalls.h>

 1 #ifdef CONFIG_FTRACE_SYSCALLS
 2 #define SYSCALL_DEFINE0(sname)                     3     SYSCALL_TRACE_ENTER_EVENT(_##sname);             4     SYSCALL_TRACE_EXIT_EVENT(_##sname);             5     static struct syscall_metadata __used             6      __syscall_meta__##sname = {                 7         .name         = "sys_"#sname,             8         .syscall_nr    = -1,    /* Filled in at boot */     9         .nb_args     = 0,                10         .enter_event    = &event_enter__##sname,    11         .exit_event    = &event_exit__##sname,        12         .enter_fields    = LIST_HEAD_INIT(__syscall_meta__##sname.enter_fields), 13     };                            14     static struct syscall_metadata __used            15      __attribute__((section("__syscalls_metadata")))    16      *__p_syscall_meta_##sname = &__syscall_meta__##sname;    17     asmlinkage long sys_##sname(void)
18 #else
19 #define SYSCALL_DEFINE0(name)     asmlinkage long sys_##name(void)
20 #endif
21 #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
22 #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
23 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
24 #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
25 #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
26 #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

在本例中,结合 sys_open 的定义:

<include\linux\syscalls.h>

asmlinkage long sys_open(const char __user *filename,
                int flags, int mode);

可以知道,SYSCALL_DEFINE3 中的数字 3 表示这个函数需要传递 3 个参数。
其中 “##”表示宏中字符直接,即:
SYSCALL_DEFINEx(3,_open,__VA_ARGS__)
其中 SYSCALL_DEFINEx 定义如下:

<include\linux\syscalls.h>

#define SYSCALL_DEFINEx(x, sname, ...)                \
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

其中 __SYSCALL_DEFINEx 定义如下:

<include\linux\syscalls.h>

#define __SYSCALL_DEFINEx(x, name, ...)                    \
    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__));            static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__));        asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__))            {                                        __SC_TEST##x(__VA_ARGS__);                        return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__));        }                                    SYSCALL_ALIAS(sys##name, SyS##name);                    static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))

在本例中如下:

<include\linux\syscalls.h>

 1 #define __SYSCALL_DEFINEx(3, _open, ...)                     2     asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__));         3     static inline long SYSC_open(__SC_DECL3(__VA_ARGS__));     4     asmlinkage long SyS_open(__SC_LONG3(__VA_ARGS__))         5     {                                 6         __SC_TEST3(__VA_ARGS__);                 7         return (long) SYSC_open(__SC_CAST3(__VA_ARGS__));     8     }                                 9     SYSCALL_ALIAS(sys_open, SyS_open);                10     static inline long SYSC_open(__SC_DECL3(__VA_ARGS__))

当我们自己定义一个不需要传递参数的系统调用的时候,可以这样定义我们的函数:

1 SYSCALL_DEFINE0(mycall)
2 {
3     printk("This is my_sys_call\n");
4     return 0;
5 }

5,对调用的函数进行解析

 1 SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
 2 {
 3     long ret;
 4     if (force_o_largefile())
 5         flags |= O_LARGEFILE;
 6     ret = do_sys_open(AT_FDCWD, filename, flags, mode);
 7     /* avoid REGPARM breakage on x86: */
 8     asmlinkage_protect(3, ret, filename, flags, mode);
 9     return ret;
10 }

open的核心处理在函数 do_sys_open() 中

 1 long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
 2 {
 3     struct open_flags op;
 4     int lookup = build_open_flags(flags, mode, &op);
 5     /*获取文件名,getname()函数内部首先创建存取文件名的内存空间,
 6       然后从用户空间把文件名拷贝到内存空间来*/
 7     char *tmp = getname(filename);
 8     int fd = PTR_ERR(tmp);
 9     if (!IS_ERR(tmp)) {
10 /*获取一个可用的 fd,该函数通过调用 alloc_fd() 函数从 fd_table 中获取一个可用的 fd,
11  *并做一些简单的初始化。
12  *需要注意的是:文件描述符 fd,只对本进程有效,也就是说这个 fd 只在该进程中可见,
13               在别的进程中可能不没有使用或是表示别的文件。
14  */
15         fd = get_unused_fd_flags(flags);
16         if (fd >= 0) {
17 /*文件描述符 fd 获取成功,则打开文件,创建一个 file 对象
18  */
19             struct file *f = do_filp_open(dfd, tmp, &op, lookup);
20             if (IS_ERR(f)) {
21                 //打开失败,释放 fd
22                 put_unused_fd(fd);
23                 fd = PTR_ERR(f);
24             } else {
25 /*如果文件已经打开,根据 inode 所指定的信息进行打开函数,函数(参数为 f)将该文件加入到
26  *文件监控的系统中。该系统是用来监控文件被打开,创建,读写,关闭,修改等操作的。
27  */
28                 fsnotify_open(f);
29 /*将文件指针安装在 fd 数组中,
30  *将 struct file *f 加入到 fd 索引位置处 的数组中。在后续过程中,有对这个文件描述符操作的话,
31  *就会通过查找该数组得到对应的文件结构,然后进行相关的操作
32  */
33                 fd_install(fd, f);
34             }
35         }
36         //释放放置从用户空间拷贝过来的文件名的存储空间
37         putname(tmp);
38     }
39     return fd;
40 }
 1 struct nameidata {
 2     struct path    path;   //当前目录的数据结构
 3     struct qstr    last;   //用以保存当前目录的名称
 4     struct path    root;
 5     struct inode    *inode; /* path.dentry.d_inode */
 6     unsigned int    flags;
 7     unsigned    seq;
 8     int        last_type;
 9     unsigned    depth;     //连接文件的深度
10     char *saved_names[MAX_NESTED_LINKS + 1];
11     /* Intent data */
12     union {
13         struct open_intent open;
14     } intent;
15 };

do_filp_open()解析

 1 struct file *do_filp_open(int dfd, const char *pathname,
 2         const struct open_flags *op, int flags)
 3 {
 4     struct nameidata nd;
 5     struct file *filp;
 6     /*根据目录打开文件
 7      */
 8     filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
 9     if (unlikely(filp == ERR_PTR(-ECHILD)))
10         filp = path_openat(dfd, pathname, &nd, op, flags);
11     if (unlikely(filp == ERR_PTR(-ESTALE)))
12         filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
13     return filp;
14 }

path_openat()

 1 static struct file *path_openat(int dfd, const char *pathname,
 2         struct nameidata *nd, const struct open_flags *op, int flags)
 3 {
 4     struct file *base = NULL;
 5     struct file *filp;
 6     struct path path;
 7     int error;
 8 ...
 9     current->total_link_count = 0;
10
11     //对路径进行解析
12     error = link_path_walk(pathname, nd);
13     if (unlikely(error))
14         goto out_filp;
15     filp = do_last(nd, &path, op, pathname);
16 ...
17 }

link_path_walk()

 1 /*
 2  * Name resolution.
 3  * This is the basic name resolution function, turning a pathname into
 4  * the final dentry. We expect ‘base‘ to be positive and a directory.
 5  *
 6  * Returns 0 and nd will have valid dentry and mnt on success.
 7  * Returns error and drops reference to input namei data on failure.
 8  */
 9 static int link_path_walk(const char *name, struct nameidata *nd)
10 {
11     struct path next;
12     int err;
13
14 ...
15         //查找文件
16      err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);
17         if (err < 0)
18             return err;
19 ...
20 }

walk_component():

 1 static inline int walk_component(struct nameidata *nd, struct path *path,
 2         struct qstr *name, int type, int follow)
 3 {
 4     struct inode *inode;
 5     int err;
 6     /*
 7      * "." and ".." are special - ".." especially so because it has
 8      * to be able to know about the current root directory and
 9      * parent relationships.
10      */
11 ...
12     //查找文件的具体实现
13     err = do_lookup(nd, name, path, &inode);
14     if (unlikely(err)) {
15         terminate_walk(nd);
16         return err;
17     }
18   ...
19 }

最后根据条件打开设备:

 1 /*
 2  * Handle the last step of open()
 3  */
 4 static struct file *do_last(struct nameidata *nd, struct path *path,
 5              const struct open_flags *op, const char *pathname)
 6 {
 7     struct dentry *dir = nd->path.dentry;
 8     struct dentry *dentry;
 9     int open_flag = op->open_flag;
10     int will_truncate = open_flag & O_TRUNC;
11     int want_write = 0;
12     int acc_mode = op->acc_mode;
13     struct file *filp;
14     int error;
15     nd->flags &= ~LOOKUP_PARENT;
16     nd->flags |= op->intent;
17 ...
18 common:
19     error = may_open(&nd->path, acc_mode, open_flag);
20     if (error)
21         goto exit;
22     //打开设备
23     filp = nameidata_to_filp(nd);
24 ...
25 }
 1 /**
 2  * nameidata_to_filp - convert a nameidata to an open filp.
 3  * @nd: pointer to nameidata
 4  * @flags: open flags
 5  *
 6  * Note that this function destroys the original nameidata
 7  */
 8 struct file *nameidata_to_filp(struct nameidata *nd)
 9 {
10     const struct cred *cred = current_cred();
11     struct file *filp;
12     /* Pick up the filp from the open intent */
13     filp = nd->intent.open.file;
14     nd->intent.open.file = NULL;
15     /* Has the filesystem initialised the file for us? */
16     if (filp->f_path.dentry == NULL) {
17         path_get(&nd->path);
18         /*填充一个 struct file 结构,打开设备的具体实现
19          */
20  filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp,
21                NULL, cred);
22     }
23     return filp;
24 }
 1 static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
 2                     struct file *f,
 3                     int (*open)(struct inode *, struct file *),
 4                     const struct cred *cred)
 5 {
 6     static const struct file_operations empty_fops = {};
 7     struct inode *inode;
 8     int error;
 9     f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |
10                 FMODE_PREAD | FMODE_PWRITE;
11     if (unlikely(f->f_flags & O_PATH))
12         f->f_mode = FMODE_PATH;
13 ...
14     if (!open && f->f_op)
15           //调用 def_chr_fops里的open函数,即chrdev_dev
16         open = f->f_op->open;
17     if (open) {
18         error = open(inode, f);   //执行该设备的 open 函数
19         if (error)
20             goto cleanup_all;
21     }
22 ...
23 }

@a [email protected]

时间: 2024-10-13 11:01:42

linux下的系统调用函数到内核函数的追踪的相关文章

&#8203;Linux下C如何调用PCI Lib函数

Linux下C如何调用PCI Lib函数 在Linux下,可以通过"setpci"和"setpci"命令来访问PCI设备的配置空间,那么能否用程序来访问PCI 配置空间呢?答案当然是肯定的,linux下提供了多个pci库以供应用程序访问.下面就以最常见的为例,从安装.使用和编译的角度分别进行说明. 安装在centos中,用超级用户权限,可用下面的命令查看到和pci访问相关的库包括:libpciaccess.i686 : PCI access librarylibpc

Linux下编译C代码,出现tan函数报错的情况

undefined reference to `tan' 但是已经包含了头文件 <math.h>了,可还是报错,说是找不到tan 这个问题的原因不是很清楚, 但是网上给出的方案,就是编译的时候 加上参数 -lm 有说l表示Lib ,m表示 math.h  , ****************************************************************************************************** 以下是一些说明 使用math.h

fork函数在内核态的追踪

1.根据搭建环境的测试流程 在虚拟机中qemu执行test_fork命令之后,程序会执行到do_fork处. 2.在ddd控制台上输入bt 命令,追踪上层函数,找到调用do_fork()的函数 可以发现上一层函数是sys_clone ,在sys_clone 处加断点,继续对函数进行分析. 3.系统执行到sys_clone处,发现在return处调用了do_fork函数.继续 追踪上层函数,找到调用sys_clone的函数.输入bt命令,发现上层函数是ptregs_clone.在entry_32.

LINUX下C语言编程调用其他函数、链接头文件以及库文件

LINUX下C语言编程经常需要链接其他函数,而其他函数一般都放在另外.c文件中,或者打包放在一个库文件里面,我需要在main函数中调用这些函数,主要有如下几种方法: 1.当需要调用函数的个数比较少时,可以直接在main函数中包含该文件,比如一个文件夹下包含add.c和main.c文件: 方法一: 文件add.c定义两个整数相加的函数,code如下: #include <stdio.h> #include <math.h> int add(int a,int b) { int z;

Linux下Python学习笔记 3:函数

一.什么是函数 函数简单来理解就是数字按一定规律重复出现的规则,我们知道圆的面积计算公式为:S = πr²,当我们知道半径r的值时,就可以根据公式计算出面积.在编程中函数被进一步封装成了方法,接收外部传入的参数来带入规则,继而返回相应的结果.那方法的封装其实是一种抽象的结果. 抽象是数学中非常常见的概念.举个例子: 计算数列的和,比如:1 + 2 + 3 + ... + 100,写起来十分不方便,于是数学家发明了求和符号∑,可以把1 + 2 + 3 + ... + 100记作: 100 ∑n n

Linux下的系统调用

1.linux的的用户态与内核态 Intel x86架构的CPU有0~3四种执行级别,0级最高,3级最低,  linux只使用0级和3级,分别表示内核态和用户态.linux中,只有内核态能访问逻辑地址为0xc0000000以上的空间.执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态.程序由用户态进入内核态的方式是中断. 系统调用的核心是使用操作系统为用户特别开放的一个中断,例如linux的int 80h中断,系统中断是由用户态

Linux下的进程类别(内核线程、轻量级进程和用户进程)以及其创建方式--Linux进程的管理与调度(四)

本文声明 日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的创建 本文中出现的,内核线程,轻量级进程,用户进程,用户线程等概念,如果不太熟悉, 可以参见 内核线程.轻量级进程.用户线程三种线程概念解惑(线程≠轻量级进程) Linux进程类别 虽然我们在区分Linux进程类别, 但是我还是想说Linux下只有一种类型的进程,那就是task_str

【学习&amp;理解】Linux下的系统调用过程

在应用程序中,很多时候都会调用到系统调用来完成一些操作,可是系统调用是在内核态下才能调用,用户态下的应用程序是无法直接调用到的,那么操作系统是怎么处理这一过程的呢? 本文的环境是基于Linux 0.11,没有查证现代操作系统是否有所变化,不过基本思路应该差不多. 过程: 先来看一张图,有个大概的理解. 首先,应用程序能直接调用的是系统提供的API,这个在用户态(Ring3)下就可做到. 然后相应的API就会将相应的系统调用号保存到eax寄存器中(这一步通过内联汇编实现),之后就是使用int 0x

Linux下的C程序,使用函数execlp运行Shell命令

通过C程序运行Shell命令,使用execlp函数 execlp的几个参数分别为:文件名,各个参数,(char*)0 其中"各个参数"部分中的第一个参数就是文件名(相当于文件名要输入两遍) 最后一个参数写(char*)0就可以了 文件b.cpp代码: 循环地输入命令→执行命令,直到按下Ctrl+C结束 #include <stdio.h> #include <string.h> #include <unistd.h> #include <std