系统调用
系统调用(system call):是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。可以说是操作系统留给用户程序的一个接口。再来说一下,linux内核是单内核,结构紧凑,执行速度快,各个模块之间是直接调用的关系。放眼望整个linux系统,从上到下依次是用户进程->linux内核->硬件。其中系统调用接口是位于Linux内核中的,如果再稍微细分一下的话,整个linux系统从上到下可以是:用户进程->系统调用接口->linux内核子系统->硬件,也就是说Linux内核包括了系统调用接口和内核子系统两部分;或者从下到上可以是:物理硬件->OS内核->OS服务->应用程序,其中操作系统起到“承上启下”的关键作用,向下管理物理硬件,向上为操作系服务和应用程序提供接口,这里的接口就是系统调用了。
一般地,操作系统为了考虑实现的难度和管理的方便,它只提供一少部分的系统调用,这些系统调用一般都是由C和汇编混合编写实现的,其接口用C来定义,而具体的实现则是汇编,这样的好处就是执行效率高,而且,极大的方便了上层调用。
可以Linux /usr/src/linux-headers-4.4.0-21/arch/arm64/include/asm 目录下找到所有的系统头文件。
acenv.h cpuidle.h insn.h mmzone.h stackprotector.h
acpi.h cpu_ops.h io.h module.h stacktrace.h
alternative.h cputype.h irqflags.h neon.h stat.h
arch_gicv3.h dcc.h irq.h numa.h string.h
arch_timer.h debug-monitors.h irq_work.h opcodes.h suspend.h
arm-cci.h device.h jump_label.h page.h sync_bitops.h
asm-offsets.h dma-mapping.h kasan.h pci.h syscall.h
assembler.h dmi.h Kbuild percpu.h sysreg.h
atomic.h efi.h kernel-pgtable.h perf_event.h system_misc.h
atomic_ll_sc.h elf.h kgdb.h pgalloc.h thread_info.h
atomic_lse.h esr.h kvm_arm.h pgtable.h timex.h
barrier.h exception.h kvm_asm.h pgtable-hwdef.h tlbflush.h
bitops.h exec.h kvm_coproc.h pgtable-types.h tlb.h
bitrev.h fb.h kvm_emulate.h processor.h topology.h
boot.h fixmap.h kvm_host.h proc-fns.h traps.h
bug.h fpsimd.h kvm_mmio.h ptrace.h uaccess.h
cacheflush.h fpsimdmacros.h kvm_mmu.h seccomp.h unistd32.h
cache.h ftrace.h kvm_psci.h shmparam.h unistd.h
cachetype.h futex.h linkage.h signal32.h vdso_datapage.h
cmpxchg.h hardirq.h lse.h smp.h vdso.h
compat.h hugetlb.h memblock.h smp_plat.h virt.h
compiler.h hw_breakpoint.h memory.h sparsemem.h word-at-a-time.h
cpufeature.h hwcap.h mmu_context.h spinlock.h xen
cpu.h hypervisor.h mmu.h spinlock_types.h
库函数
顾名思义是把函数放到库里。是把一些常用到的函数编完放到一个文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就可以了。一般是放到lib文件里的。一般是指编译器提供的可在c源程序中调用的函数。可分为两类,一类是c语言标准规定的库函数,一类是编译器特定的库函数。(由于版权原因,库函数的源代码一般是不可见的,但在头文件中你可以看到它对外的接口)
libc中就是一个C标准库,里面存放一些基本函数,这些基本函数都是被标准化了的,而且这些函数通常都是用汇编直接实现的。
库函数一般可以概括的分为两类,一类是随着操作系统提供的,另一类是由第三方提供的。随着系统提供的这些库函数把系统调用进行封装或者组合,可以实现更多的功能,这样的库函数能够实现一些对内核来说比较复杂的操作。比如,read()函数根据参数,直接就能读文件,而背后隐藏的比如文件在硬盘的哪个磁道,哪个扇区,加载到内存的哪个位置等等这些操作,程序员是不必关心的,这些操作里面自然也包含了系统调用。而对于第三方的库,它其实和系统库一样,只是它直接利用系统调用的可能性要小一些,而是利用系统提供的API接口来实现功能(API的接口是开放的)。部分Libc库中的函数的功能的实现还是借助了系统掉调用,比如printf的实现最终还是调用了write这样的系统调用;而另一些则不会使用系统调用,比如strlen, strcat, memcpy等。
库函数是在系统调用上的一层包装,运行在用户态(user mode)。
所以虽然最终所有的工作都是系统调用做的,但是我们更通常的做法是调用库函数,有以下几个原因:
库函数提供了抽象,抽象是个好东西,可以让我们把更多的注意力集中在要解决问题的核心。
库函数给我们提供的接口更人性化,所以调用起来更方便。
调用库函数更安全,内存管理不用自己太操心。
调用库函数效率更高,程序跑的更快。虽然库函数最终是调用系统函数,但是库函数会比我们用更好的方式方法调用系统函数。
库函数不光可以在系统调用的基础上包装,也可以在其他库函数的基础上包装,提供更高级的抽象,更强大的功能。比如OGRE对OpenGL的包装,Qt对Xlib的包装。
最后介绍两个工具,strace和ltrace,strace查看我们的可执行文件调用了哪些系统调用,ltrace查看可执行文件调用了哪些库函数。用法很简单,直接在命令后面跟可执行文件路径就好了。