浅谈C语言函数调用与系统调用

1. 函数调用和系统调用概述

1.1 定性的去区分函数调用和系统调用

很多初学C语言的同学,亦或者开发中很少接触系统底层的同学可能会认为函数调用以及系统调用是一回事。因为在应用程序,两者都被抽象成接口去给应用程序调用。其实函数调用和系统调用还是有区别,我们通过下图先有个全局的了解!

图1-1 系统功能模块关联图

 

从图1-1 我们可以知道应用程序访问内核,主要通过两种方式:中断和系统调用接口。

其一,中断是置于程序流程之外的,所以这种方式并不是我们访问内核的普遍方式,更多的是因为程序异常而引起的中断;

其二,就是系统调用接口,例如使用 open、read 以及 write 去操作文件数据(相当于去访问硬盘)。

而函数调用是无法直接访问内核的,函数调用需要访问内核,也只能通过系统调用的方式。

所以,我们可以通过这种方式定性地去区分函数调用和系统调用: 接口直接访问内核的属于系统调用,其他则为函数调用

1.2 为什么要封装好系统调用

我们知道,32位 linux系统为了保证程序独立性,给每一个进程分配独立的4G地址空间(虚拟内存空间),但是4G地址空间又被划分为两个空间:用户空间以及内核空间,如下图:

图1-2 进程地址空间划分图

用户空间,存储的是一些进程常见的数据,例如代码、变量数据、堆区和栈区等,需要注意的是内核空间,虽然说系统给每一个进程都分配1G大小的内核空间,但是实际上,内核空间是操作系统中进程公用的。公用就会带来一个现实性的问题,就比如我们可以在自己的房间干任何事情(用户空间),去不能在公共场合(内核空间)为所欲为,活动在公共场合(内核空间),我们的行为会受到限制,对于操作系统也一样道理,内核空间属于进程公用,进程去访问内核必须保证不会给其他进程造成影响。

如何去保证进程操作系统中公用的内核空间不会出现非法操作,打个比方说:如何保证银行内部数据不被非法操作,银行是提供一个ATM给你,你只能选择上面的接口操作。操作系统也“学习”了这样的模式,封装接口,保证数据操作正确性!

不仅仅应用程序无法直接访问内核空间,只能通过系统调用访问内核空间,而且内核对用户空间的数据的不信任,对于通过系统调用的传递过来的数据也会做相应的检查,确保内核安全,确保内核安全是必须的,内核属于系统的软件核心,内核崩溃会导致整个软件系统的崩溃。为了保证应用程序传递的数据合法性以及内核数据的绝密性,通常会使用copy_from_user去检验和拷贝传递过来的数据,使用copy_to_user()检验和拷贝数据给应用程序。

2.函数调用

2.1 什么叫做函数调用

int add(int a, int b){

    return(a + b);
}

int main(){

    int sum;
    
    sum = add(1 + 2);
    
    return 0;
}

如上是一个最简单的函数调用过程,了解什么叫函数调用,我们可以通过了解什么是函数?

函数(function)是完成特定任务的独立程序代码单元。

                                                                                                                                                     ----《C Primer Plus》

再来了解为啥需要使用函数?

首先,使用函数可以省去编写重复代码的苦差。如果程序多次完成某一项。如果程序需要多次完成某项任务,那么只需编写一个适合的函数,就可以在需要时使用这个函数,或者在不同的程序中使用该函数,就像许多程序使用putchar() 一样。

其次,即使程序只完成某项任务一次,也值得使用函数。因为函数使程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。

                                                                                                                                                     ----《C Primer Plus》

函数调用是很好理解的,提高代码重用性以及模块性,调用相对应函数,得到某种结果。

2.1 函数调用的过程

函数调用会发生函数压栈以及出栈过程,具体可以参考如下博文和视频(博主对汇编不熟悉,所以就不具体分析了,以免误人子弟!)。

浅析函数的调用过程

汇编中的函数调用中栈的工作过程

3.系统调用

3.1 什么叫做系统调用

为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)

                                                                                                                                         ----《Linux内核设计与实现》

3.2 系统调用的过程

系统调用和函数调用在应用程使用几乎没有差别,但是内部实现过程是完全不同的,函数调用是通过函数入口地址直接跳转函数服务程序,而系统调用并非直接跳转系统调用服务程序,因为内核地址对于应用程序是不可用的,需要通过中断的方式进入内核态,再通过一系列的操作找到对应的服务程序。

我们可以通过下图有一个了解:

图3-1 系统调用过程流程图

希望更详细的了解系统调用过程可以看看下面这篇博文:

ioctl系统调用过程(深入Linux(ARM)内核源码)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章