1 系统调用
系统调用(system call)其实是 Linux 内核提供给应用层的应用编程接口(API),是 Linux 应用层进入内核的入口。
通过系统调用, Linux 应用程序可以请求内核以自己的名义执行某些事情,譬如打开磁盘中的文件、读写文件、关闭文件以及控制其它硬件外设。
通过系统调用 API,应用层可以实现与内核的交互,其关系可通过下图简单描述:
内核提供了一系列的服务、资源、支持一系列功能,应用程序通过调用系统调用 API 函数来使用内核提供的服务、资源以及各种各样的功能。
嵌入式 Linux 硬件平台下的软件开发来说,我们大可将编程分为三种,分别为裸机编程、 Linux 驱动编程以及 Linux 应用编程。
一般把没有操作系统支持的编程环境称为裸机编程环境,譬如单片机上的编程开发,编写直接在硬件上运行的程序,没有操作系统支持;
狭义上 Linux 驱动编程指的是基于内核驱动框架开发驱动程序,驱动开发工程师通过调用 Linux 内核提供的接口完成设备驱动的注册,驱动程序负责底层硬件操作相关逻辑;
而 Linux 应用编程(系统编程)则指的是基于 Linux 操作系统的应用编程,在应用程序中通过调用系统调用 API 完成应用程序的功能和逻辑,应用程序运行于操作系统之上。通常在操作系统下有两种不同的状态:内核态和用户态,应用程序运行在用户态、而内核则运行在内核态。
2 库函数
系统调用是内核直接向应用层提供的应用编程接口,譬如 open、 write、read、 close 等,关于这些系统调用后面会给大家进行详细介绍。编写应用程序除了使用系统调用之外,我们还可以使用库函数。
库函数也就是 C 语言库函数, C 语言库是应用层使用的一套函数库,在 Linux 下,通常以动态(.so)库文件的形式提供,存放在根文件系统/lib 目录下, C 语言库函数构建于系统调用之上,也就是说库函数其实是由系统调用封装而来的,当然也不能完全这么说,原因在于有些库函数并不调用任何系统调用,譬如一些字符串处理函数 strlen()、 strcat()、 memcpy()、 memset()、 strchr()等等;而有些库函数则会使用系统调用来帮它完成实际的操作,譬如库函数 fopen 内部调用了系统调用 open()来帮它打开文件、库函数 fread()就利用了系统调用 read()来完成读文件操作、 fwrite()就利用了系统调用 write()来完成写文件操作 。
Linux 系统内核提供了一系列的系统调用供应用层使用,我们直接使用系统调用就可以了,那为何还要设计出库函数呢?事实上,有些系统调用使用起来并不是很方便,于是就出现了 C 语言库,这些 C 语言库函数的设计是为了提供比底层系统调用更为方便、更为好用、且更具有可移植性的调用接口。
- 库函数是属于应用层,而系统调用是内核提供给应用层的编程接口,属于系统内核的一部分;
- 库函数运行在用户空间,调用系统调用会由用户空间(用户态)陷入到内核空间(内核态);
- 库函数通常是有缓存的,而系统调用是无缓存的,所以在性能、效率上,库函数通常要优于系统调用。
- 可移植性:库函数相比于系统调用具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于 C 语言库函数来说,由于很多操作系统都实现了 C 语言库, C 语言库在不同的操作系统之间其接口定义几乎是一样的,所以库函数在不同操作系统之间相比于系统调用具有更好的可移植性。
3 标准C语言函数库
在 Linux 系 统 下 , 使 用 的 C 语 言 库 为 GNU C 语 言 函 数 库 ( 也 叫 作 glibc , 其 网 址 为http://www.gnu.org/software/libc/),作为 Linux 下的标准 C 语言函数库。
3.1 确定Linux系统的glibc版本
C 语言库是以动态库文件的形式提供的,通常存放在/lib 目录,它的命名方式通常是libc.so.6,不过这个是一个软链接文件,它会链接到真正的库文件。
进入到 Ubuntu 系统的/lib 目录下,笔者使用的 Ubuntu 版本为 16.04,在我的/lib 目录下并没有发现libc.so.6 这个文件,其实是在/lib/x86_64-linux-gnu 目录下,进入到该目录:
可以看到 libc.so.6 链接到了 libc-2.23.so 库文件, 2.23 表示的就是这个 glibc 库的版本号为 2.23。除此之外,我们还可以直接运行该共享库来获取到它的信息,如下所示:
从打印信息可以看到,笔者所使用的 Ubuntu 系统对应的 glibc 版本号为 2.23。
4.main函数
对学习过 C 语言编程的读者来说,譬如单片机编程、 Windows 应用编程等, main 函数想必大家再熟悉不过了,很多编程开发都是以 main 函数作为程序的入口函数,同样在 Linux 应用程序中, main 函数也是作为应用程序的入口函数存在, main 函数的形参一般会有两种写法,如果执行应用程序无需传参,则可以写成如下形式:
1
2
3
4 int main(void)
{
/* 代码 */
}如果在执行应用程序的时候需要向应用程序传递参数,则写法如下:
1
2
3
4 int main(int argc, char **argv)
{
/* 代码 */
}argc 形参表示传入参数的个数,包括应用程序自身路径和程序名,譬如运行当前目录下的 hello 可执行文件,并且传入参数,如下所示:
1 ./hello 112233那么此时参数个数为 2,并且这些参数都是作为字符串的形式传递给 main 函数:
argv[0]等于”./hello”
argv[1]等于”112233”
有传参时 main 函数的写法并不只有这一种,只是这种写法最常用,对于其它的写法,后面学习过程中如果遇到了再给大家进行讲解,这里暂时先不去管。
5.开发环境
分为三篇内容,分为入门篇、提高篇以及进阶篇。
因为是 Linux 应用编程,所以本书将会在 Linux 操作系统环境下编写示例代码,为了同正点原子的其它开发文档保持一致,选择 Ubuntu 操作系统,推荐大家使用 16.04 或 14.04 版本的 Ubuntu 系统。
在 Linux 操作系统下,使用 vscode,与正点原子的驱动开发文档保持一致; vscode 的安装方法和使用方法本书不再介绍。
入门篇章节内容中,主要介绍 Linux 应用编程所涉及到的各个知识点以及各种系统调用和库函数,本部分内容所编写的示例代码,均是在 Ubuntu 系统下进行测试、验证,也就是在 Ubuntu 系统下执行测试程序以验证所学知识内容。所以在该部分内容中,本书使用 vscode+gcc 的方式开发应用程序, vscode作为代码编辑器、 gcc 作为编译器,使用 vscode 编写完代码之后再用 gcc 编译源码,得到可在 PC 端 Ubuntu系统下运行的可执行文件。
提高篇章节内容中,将会以正点原子ALPHA/Mini I.MX6U 开发板为例,介绍如何编写应用程序控制开发板上的各种硬件外设,所以编写的应用程序是需要在开发板上运行的, ALPHA/Mini I.MX6U 开发板是 ARM 架构硬件平台,所以源码需要使用交叉编译工具(ARM 架构下的 gcc 编译器)进行编译,得到可在开发板上运行的可执行文件。所以在第二篇章节内容中,使用 vscode+ARM gcc(交叉编译工具)的方式开发应用程序。
入门篇和提高篇章节内容中所编写的示例代码只有单个.c 源文件,但是对于一个真正的 Linux 应用程序项目来说,其原文件的个数肯定不止一个,可能有几十上百个,这个时候可能就需要用到 Makefile 来组织、管理工程源文件。直接通过 Makefile 来组织工程,对于一个大型软件工程来说通常是比较困难的,需要程序员对 Makefile 能够熟练使用,譬如 Linux 内核源码、 U-Boot 源码等都是直接使用 Makefile 进行管理。对于一般程序员来说,很难做到熟练使用 Makefile。在第三篇(进阶篇)章节内容中,会向大家介绍 cmake工具,使用 cmake 来管理我们的源码工程,相比于直接使用 Makefile,使用 cmake 会打打降低难度,提高我们的开发效率!
进阶篇章节内容中,介绍一些 Linux 应用编程实战小项目,通过学习进一步提升 Linux 应用编程能力,本部内容将会使用 cmake+vscode 进行开发,其实 cmake 在实际的项目当中用的还是比较多的,譬如很多的开源软件都会选择使用 cmake 来构建、配置工程源码,学习了之后,相信大家在今后的工作当中也会用到。
关于本小节的内容到这里就结束了,下一章开始将正式进入到应用编程学习之路,大家加油!