高端的程序员往往采用最朴素的编程方式。
能不能复用别人的代码,避免重复造轮子,是衡量程序员工作效率、是否能按时下班的关键。我们编写一个add()函数,只要封装得好,就可以给很多人使用,这样就可以减少他人重复劳动,避免重复造轮子。
// add.c
int add(int a, int b)
{
return a + b;
}
//add.h
int add(int a, int b);
其他的开发者,需要调用add()时,只要在自己的程序中使用#include包含对应的头文件add.h,就可以直接调用add()函数,不用自己再写一遍了。
我们将很多类似的函数打包,归档成一个库,引出对应的头文件声明,就可以供很多人调用和使用了。
# gcc -c add.c -o add.o
# gcc -c sub.c -o sub.o
# ar rcs libmath.a add.o sub.o
将这些库,放到操作系统的指定官方目录(如/lib、/usr/lib),随操作系统一起发布,对应函数的声明头文件(add.h sub.h),也放在操作系统的指定目录(如/usr/include)。程序开发者就可以在他们的程序中直接使用这些函数了:
#include <add.h>
int main(void)
{
int sum = 0;
sum = add(3, 4);
return 0;
}
然后我们在编译程序时指定需要的链接库就可以了:
# gcc main.c -L. -lmath
如果一些库在编译参数设置时是默认链接的,如C标准库,就不需要显式指定要链接的库了,直接编译程序就可以了。
如果从软件复用的角度看操作系统,操作系统其实也可看做一个库:操作系统对计算机的资源做了各种封装:任务创建、内存管理、文件系统、网络通信,并引出一系列接口给用户使用,避免用户重复造轮子。以uc/os为例,我们需要创建一个多任务程序,就可以直接将uc/os源码直接导入到我们的项目中,然后直接调用uc/os提供的接口即可:
#include "ucos_ii.h"
int main(void)
{
OSInit(); //初始化OS
OSTaskCreate();//创建一个任务
OSStart(); //开启任务调度
}
到了Linux时代,操作系统发布商、应用程序开发者开始分离,由不同的团队和公司完成开发。用户可以自己下载自己喜欢的软件安装到电脑上,成千上万的开发者,成千上万的软件,对安全构成了很大的挑战。为了安全起见,Linux使用了权限管理:操作系统和应用程序分别运行在内核态和用户态,具有不同的权限:应用程序运行在普通权限下,不能访问硬件,而操作系统则运行在特权模式下,可以直接操作系统,读写寄存器,切换CPU的工作模式...
有了权限管理,Linux内核实现的各种函数接口,应用程序就无法像uc/os那样直接调用了。但Linux内核向应用程序提供了系统调用的访问接口:软中断指令,如X86的处理器的INT指令,ARM处理器的SWI/SVC指令,应用程序可以通过软中断,陷入Linux内核,去执行内核实现的各种的接口函数。
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
printf("fork failed!\n");
else if(pid == 0)
printf("child process\n");
else
printf("parent process\n");
return 0;
}
其中的fork只是一个系统调用接口函数,真正的实现是在内核中的sys_fork()中实现的。C标准库glibc再对这些接口进行封装,就成了我们大家熟悉的read、write、open、close等系统调用函数了,用户就可以像调用普通函数一样,通过这些接口,去调用Linux内核中实现的各种系统调用函数了。
想要了解系统调用更多细节:比如在X86、ARM架构上的不同实现,CPU、操作系统和glibc三者是如何配合的,包括最新版本内核使用的快速系统调用、虚拟系统调用、VDSO是怎么回事,请关注 《Linux内核编程》第03期:系统调用。
本期课程主要内容:
- 系统调用的基本概念
- 软中断:系统调用的入口(ARM)
- 软中断:系统调用的入口(X86)
- 系统调用的接口封装:glibc
- 系统调用的接口封装:syscall
- 系统调用流程分析
- 添加一个系统调用
- 系统调用的开销
- 快速系统调用
- 系统系统调用:vsyscall
- 虚拟动态共享对象:VDSO
- 从系统调用角度看文件的读写流程
本期课程共12个课时,视频总大小1.56GB,总时长3小时9分钟。