从内核看系统调用

本周学习了孟宁老师的《Linux内核分析》,按照课程要求,做实验如下:

首先使用gdb跟踪一个系统调用,我们选择上周实验所写的代码,修改这两段代码成两个系统调用,放入根文件系统中,作为本次实验将要观察的系统调用。

修改代码如下:

1) c实现的系统调用

int mkdir_c(int argc, char **argv)
{
    if(argc != 2)
        printf("Illegal parameter!!\n");

    if(mkdir(argv[1]) == -1)
    {
        printf("Mkdir error!!\n");
        return -1;
    }
    printf("Mkdir success!!\n");
    return 0;
}

2) 使用汇编语言实现的系统调用

int mkdir_asm(int argc, char **argv)
{
    if(argc != 2)
        printf("Illagel parameters!!\n");

    int ret = 0;
    asm volatile(
            "mov $0, %%eax\n\t"
            "mov $0x27, %%eax\n\t"
            "int $0x80\n\t"
            "mov %%eax, %0\n\t"
            :"=m"(ret)
            :"b"(argv[1])
            );

    if(ret == -1)
    {
        printf("Mkdir Error!!\n");
        return -1;
    }
    printf("Mkdir success!!\n");
    return 0;
}

打开实验楼环境,进入menu目录下

cd /home/shiyanlou/LinuxKernel/menu

打开test.c文件,假如上面两段代码,如图所示:

在main函数中添加两行代码注册这两个系统调用:

重新编译,制作成根文件系统镜像。使用qemu仿真环境加载内核和根文件系统,启动系统。为了方便,我们使用把这一系列命令都写入Makefile文件中,所以只需在终端执行如下命令:

make rootfs

就能完成全部动作:

我们可以执行我们所添加的命令了。

接下来我们使用gdb工具来对系统调用进行调试。

首先使用qemu进入调试模式,冻结住内核:

启动gdb,加载调试符号表,开始进行调试:

在system_call函数处设置了断点,但是无法在此处停止,因为system_call函数所在的entry_32.s文件是汇编代码,gdb对汇编代码的调试能力有限。

为了了解系统调用机制的处理过程,我们对system_call这段代码进行阅读和分析:

具体的代码参见:http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/kernel/entry_32.S

首先内核要初始化系统调用机制,我们知道start_kernel是内核的入口地址,在这个函数中系统完成一系列的初始化工作,所以系统调用机制的初始化工作也必定在这个函数中,其中的trap_init函数就是负责对系统调用机制进行初始化,

// in init/main.c   start_kernel553  /*
554     * These use large bootmem allocations and must precede
555     * kmem_cache_init()
556     */
557    setup_log_buf(0);
558    pidhash_init();
559    vfs_caches_init_early();
560    sort_main_extable();
561    trap_init();
562    mm_init();

我们进入trap_init函数:

837
838#ifdef CONFIG_X86_32
839    set_system_trap_gate(SYSCALL_VECTOR, &system_call);
840    set_bit(SYSCALL_VECTOR, used_vectors);
841#endif
842

这一段代码把中断向量表中的0x80号中断指向system_call代码段处,我们进入SYSCALL_VECTOR可以看到这个宏代表的就是0x80:

50#ifdef CONFIG_X86_32
51# define SYSCALL_VECTOR            0x80
52#endif
53

接下来我们还分析system_call这段代码:

489    # system call handler stub
490    ENTRY(system_call)
491    RING0_INT_FRAME            # can‘t unwind into user space anyway
492    ASM_CLAC
493    pushl_cfi %eax            # save orig_eax
494    SAVE_ALL           //保存当前进程上下文,因为系统调用本身就是一种中断,所以和中断机制一样需要保存当前进程的一些信息,以便执行完系统调用后恢复现场
495    GET_THREAD_INFO(%ebp)
496                    # system call tracing in operation / emulation
497    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
498    jnz syscall_trace_entry
499    cmpl $(NR_syscalls), %eax
500    jae syscall_badsys
501syscall_call:
502    call *sys_call_table(,%eax,4)    //调用系统调用对应的处理函数
503syscall_after_call:
504    movl %eax,PT_EAX(%esp)        # store the return value
505syscall_exit:
506    LOCKDEP_SYS_EXIT
507    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don‘t miss an interrupt
508                    # setting need_resched or sigpending
509                    # between sampling and the iret
510    TRACE_IRQS_OFF
511    movl TI_flags(%ebp), %ecx
512    testl $_TIF_ALLWORK_MASK, %ecx    # current->work    //检测当前的任务
513    jne syscall_exit_work    //是否跳到syscall_exit_work来处理
514
515restore_all:
516    TRACE_IRQS_IRET

我们来看syscall_exit_work这段代码:

        # perform syscall exit tracing
655    ALIGN
656    syscall_exit_work:
657    testl $_TIF_WORK_SYSCALL_EXIT, %ecx
658    jz work_pending    //跳转至信号处理,work_reached进程调度处理等
659    TRACE_IRQS_ON
660    ENABLE_INTERRUPTS(CLBR_ANY)    # could let syscall_trace_leave() call
661                    # schedule() instead
662    movl %esp, %eax
663    call syscall_trace_leave
664    jmp resume_userspace
665END(syscall_exit_work)

最后我们绘制流程图来表述从system_call到iret这一段代码的执行流程:

以上便是我对本次试验的全部理解,如有错误,还望指正。

Allen 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

时间: 2024-07-31 14:32:19

从内核看系统调用的相关文章

linux内核增加系统调用--Beginner‘s guide

Linux内核中设置了一组用于实现系统功能的子程序,称为系统调用.系统调用和普通库函数调用非常相似明知是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态. 一般的,进程是不能访问内核的,它不能访问内核所占用内存空间也不能调用内核函数.这被称为保护模式.为了和用户空间上运行的进程进行交互,内核提供一组接口.通过该接口应用程序可以访问硬件设备和其他操作系统资源. 实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序肆意妄为. 系统调用在用户空间和硬

[linux内核]ARM-Linux系统调用

1,系统调用的概念: 是用户空间访问内核的唯一手段,系统调用依靠软件中断实现,每个系统调用被赋予一个系统调用号,用来指明要执行哪个系统调用. 2,系统调用的实现:系统调用是通过软中端(SWI)实现的,SWI指令SWI指令的格式为:SWI{条件} 24位的立即数SWI指令用于产生软件中断,以便用户程序能调用操作系统的系统例程.操作系统在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数指定用 户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中24位的立即数被忽略时,用户程序

深入理解Linux内核-系统调用

系统调用:用户态进程向内核发出的,实现用户态进程调用硬件设备的函数或者中断:优点:使编程更容易,将用户从学习硬件设备的低级编程特性中解放:提高系统到安全性,内核在满足请求之前可以做正确性检查:提高可移植性: 系统调用与API调用的区别:1.API调用是一个函数定义:系统调用是通过软中断向内核发出的明确请求2.内核不可以访问库函数 内核中,返回值为正数或者0表示系统调用成功结束,否则表示出错条件 进入内核态的两种方式:1.执行 int $0x80 指令 :2.执行 sysenter 指令内核推出系

向linux内核添加系统调用新老内核比较

2.6内核 1>修改linux-source-2.6.31/kernel/sys.c文件,在文件末尾添加系统响应函数.函数实现如下: asmlinkage int sys_mycall(int number) { printk("这是我添加的第一个系统调用"); return number; } 2>在linux-source-2.6.31/arch/x86/kernel/syscall_table_32.S 中添加:.long sys_mycall 如: .long sy

向linux内核加入系统调用新老内核比較

2.6内核 1>改动linux-source-2.6.31/kernel/sys.c文件,在文件末尾加入系统响应函数.函数实现例如以下: asmlinkage int sys_mycall(int number) { printk("这是我加入的第一个系统调用"); return number; } 2>在linux-source-2.6.31/arch/x86/kernel/syscall_table_32.S 中加入:.long sys_mycall 如: .long

Linux内核(四)系统调用

转载请注明出处:jiq?钦's technical Blog 什么是系统调用? 系统调用--内核和用户应用程序的桥梁,中间人. 系统调用就是内核实现的一系列函数,这些函数提供了一套固定的接口,通过这套接口,用户程序可以访问系统硬件和操作系统的资源,即内核提供的服务. 为什么提供系统调用? 用户空间只能通过系统调用来访问内核提供的服务的根本原因是为了对系统进行"保护",因为我们知道Linux的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离.用户进程在通常情况下

Linux学习(二)之内核、系统调用、库

这篇博客主要介绍Linux中几个常见的概念 Kernal:内核:我们知道计算机的硬件包括:cpu.内存.外存等等 系统想要统一资源分配就需要用到内核!内核是将计算机的硬件资源抽象出来,提供给其他的软件,可以理解为平台. 而内核提供的使用方式就是:系统调用(System call) 库: 因为系统调用的功能有限,所以,人们将多个系统调用功能封装起来,提供更高级的功能接口供开发者调用. 这些功能接口组成的就是库! 而库文件不能单独执行,必须被调用才能执行. Windows系统的库文件类型是:.dll

Linux3.18.6内核添加系统调用

1.将已经下载好的内核解压,我的目录是 /home/shangsongchao/LinuxKernel/testlinux-3.18.6/linux-3.18.6 2.添加系统调用表: 在/home/shangsongchao/LinuxKernel/testlinux-3.18.6/linux-3.18.6/arch/x86/syscalls目录下,打开syscall_32.tbl 添加三个系统调用,如图358,359,360  不需要像Linux 2.6的内核一样,在<asm/unistd.

《Linux内核分析》 week6作业-Linux内核fork()系统调用的创建过程

一.进程控制块PCB-stack_struct 进程在操作系统中都有一个结构,用于表示这个进程.这就是进程控制块(PCB),在Linux中具体实现是task_struct数据结构,它主要记录了以下信息: 状态信息,例如可执行状态.就绪状态.阻塞状态等. 性质,由于unix有很多变种,进行有自己独特的性质. 资源,资源的链接比如内存,还有资源的限制和权限等. 组织,例如按照家族关系建立起来的树(父进程.子进程等). task_struct结构体内容非常庞大,暂时没有去分析源代码,以后有时间再去研究