来自共创官的学习笔记分享|使用C库函数控制ELF1开发板的LED
在嵌入式Linux系统开发中,通过编程控制硬件资源是至关重要的技能之一,今天跟各位小伙伴分享一篇专注于介绍如何使用C库函数控制ELF 1开发板LED的学习笔记。
希望通过这篇学习笔记,让屏幕前的小伙伴能深入理解Linux内核对于底层硬件资源管理的抽象机制,为进行复杂的嵌入式系统开发奠定基础。
一、系统调用与库函数
(一)系统调用
系统调用(System Call)是操作系统内核提供的函数,在内核态运行(Kernel Mode),是操作系统为用户提供的一些接口。它通过软中断向内核态发出一个明确的请求。有一些任务需要进程跑在内核态才能执行,比如和硬件打交道。所以进程调用系统调用就能让自己运行在内核态从而执行这些类似的任务。系统调用实现了用户态进程和硬件设备之间的大部分接口。
常见系统调用
Open, Close, Read, Write, Ioctl,Fork,Clone,Exit,Getpid,Access,Chdir,Chmod,Stat,Brk,Mmap等,需要包含Unistd.h等头文件。
(二)库函数
库函数位于系统调用的上层,扮演着封装和抽象的角色,运行在用户态(User Mode),旨在为程序员提供一种更为便捷的方式来调用真正实现底层功能的系统调用。这些库函数充当了用户态服务的供给者,其功能实现机制各异:有的库函数可能整合并包装了一个或多个不同的系统调用,而有的库函数则能够直接在用户态提供所需服务,无需进一步调用任何系统调用。
(三)区别
系统调用通常不可替换,而库函数通常可替换
普通的库函数调用由函数库或用户自己提供,因此库函数是可以替换的。例如,对于存储空间分配函数malloc,如果不习惯它的操作方式,我们完全可以定义自己的malloc函数。
系统调用通常提供最小接口,而库函数通常提供较复杂功能
例如sbrk系统调用分配一块空间给进程,而malloc则在用户层次对这以空间进行管理。
系统调用运行在内核空间,而库函数运行在用户空间
因为系统调用属于内核,和库函数不属于内核。因此,如果当用户态进程调用一个系统调用时,CPU需要将其切换到内核态,并执行一个内核函数。
内核调用都返回一个整数值,而库函数并非一定如此
在内核中,整数或0表示系统调用成功结束,而负数表示一个出错条件。而出错时,内核不会将其设置在errno,而是由库函数从系统调用返回后对其进行设置或使用。
POSIX 标准针对库函数而不是系统调用
判断一个系统是否与POSIX需要看它是否提供一组合适的应用程序接口,而不管其对应的函数是如何实现的。因此从移值性来讲,使用库函数的移植性较系统调用更好。
系统调用运行时间属于系统时间,库函数运行时间属于用户时间
调用系统调用开销相对库函数来说更大
很多库函数本身都调用了系统调用,这得益于双缓冲的实现,在用户态和内核态,都应用了缓冲技术,对于文件读写来说,调用库函数,可以大大减少调用系统调用的次数。而用户进程调用系统调用需要在用户空间和内核空间进行上下文切换,开销较大。如此以来,库函数的开销也就会比直接调用系统调用小了。另外一方面,库函数同样会对系统调用的性能进行优化。
二、使用C库函数控制LED
(一)实验代码
参考3.2.1.2 文件 I/O 的方式控制 LED的例程将使用系统调用的部分改为使用库函数来实现。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define LED1_BRIGHTNESS "/sys/class/leds/led1/brightness"
#define LED2_BRIGHTNESS "/sys/class/leds/led2/brightness"
#define LED3_BRIGHTNESS "/sys/class/leds/led3/brightness"
int main()
{
FILE *fd1, *fd2, *fd3;
fd1 = fopen(LED1_BRIGHTNESS, "w");
if(fd1 < 0) {
printf("Fail to Open %s device\n", LED1_BRIGHTNESS);
exit(1);
}
fd2 = fopen(LED2_BRIGHTNESS, "w");
if(fd2 < 0) {
printf("Fail to Open %s device\n", LED2_BRIGHTNESS);
exit(1);
}
fd3 = fopen(LED3_BRIGHTNESS, "w");
if(fd3 < 0) {
printf("Fail to Open %s device\n", LED3_BRIGHTNESS);
exit(1);
}
while(1) {
fwrite("1",3,1,fd1);
fflush(fd1);
sleep(1);
fwrite("0",1,1,fd1);
fflush(fd1);
fwrite("1",3,1,fd2);
fflush(fd2);
sleep(1);
fwrite("0",1,1,fd2);
fflush(fd2);
fwrite("1",3,1,fd3);
fflush(fd3);
sleep(1);
fwrite("0",1,1,fd3);
fflush(fd3);
}
fclose(fd1);
fclose(fd2);
fclose(fd3);
return 0;
}
(二)编译、测试
将代码编译后拷贝到ELF 1开发板进行测试。
执行LED2,查看结果
可以看到LED按照预期循环点亮
三、总结
本次实验使用C库函数实现了对LED的控制,通过一个简单的示例来感受系统调用与库函数的区别。但是代码中还有需要注意的地方。
代码中调用fwrite函数写入内容时,它可能只是把内容保存到了C库的缓冲区,并没有执行真正的系统调用write函数把内容写入到设备文件,这种情况下LED灯的状态是不会被改变的,代码中在fwrite函数后调用了fflush要求立刻把缓冲区的内容写入到文件,确保 执行了相应的操作。在实验时可以尝试把代码中的fflush都注释掉,这种情况下有极大的几率是无法正常改变LED灯状态的。
如果不考虑操作的时间开销,其实控制硬件更推荐的做法是,每次控制LED灯都使用fopen—fwrite—fclose的流程,这样就不需要考虑flseek、fflush的问题了,但最推荐的还是直接通过系统调用来控制硬件的方式。