RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网
【粉丝群】824412014
【实验平台】:迅为RK3588开发板
【内容来源】《iTOP-3588开发板系统编程手册》
【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载
【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板
前言
各位同学好,很荣幸能在这里和大家见面,如果大家之前对本手册稍有关注,会发现如今已经是第二个版本了,相较于初版,作者在保证知识点和内容未大规模修改的前提下,又重新加入了部分作者自己的思考,旨在让读者更好的理解书中的内容。
下面对如何学习系统编程进行陈述(也可类比到其他知识的学习,学习的本质是相通的):
要学习一门新的学科,作者认为了解它的发展历史和演变过程是必不可少的一个过程,学科的历史往往与学科奠基人、重要发现和突破密切相关。了解这些历史背景可以帮助学习者更好地理解学科的价值和意义,激发自身的学习兴趣和热情。虽然在本手册中并没有加入Linux的发展和C系统编程演变历史相关的内容,但是作者仍旧推荐大家对此进行一定的了解。
了解了发展历史和演变过程之后,自然而然要对该学科本身进行了解,在此提出来三个问题,系统编程是什么?为什么要进行系统编程的学习呢?学习了系统编程最后能做什么?作者在最开始学习的时候并没有对上述三个问题有很清晰的答案(甚至都没有想过,只是为了学习而学习),所以无法避免的走了许多的弯路,幸好如今已经意识到了问题所在,及时补救还不算太晚。要学习一件东西,只有对它有了一个初步认知并且知晓它的作用,才不会半途而废,从而在这一道路上越走越远。
下面对本手册的结构进行分析,本手册总共分为了三个部分,第一个部分会对系统编程以及一些基础进行介绍(对应的就是上一段文字)。第二个部分会对一些基础的系统调用、IO、进程、信号等知识进行讲解,而第三个部分为系统编程实战,利用第二部分所学到的知识在开发板硬件上实现部分功能,例如LED、串口、PWM等。三个部分层层递进,一步步加深和熟练系统编程。
作者认为学习一定是要有目的性的,一般情况下分为“是什么”、“为什么”、“怎么做”三种目的性疑问,以最常见的系统调用API open()函数为例可以提出以下三个问题:
问题1:系统调用open()函数是什么?
问题2:系统调用open()函数的作用是什么?
问题3:系统调用open()函数要怎样进行使用?
对于该章节学习的过程就是解决上述问题的过程,可以极大提高学习的效率。
本手册的目的旨在帮助刚入门的小白进行入门规划和学习,为系统编程基础指导手册。如果您已经是高级资深工程师,本手册并不会对您有实质性的帮助,直接跳过即可。虽然本手册偏向于小白,但仍旧需要一些C语言基础和Linux基本操作知识才能开始本手册的学习,上面两个必要条件在网上已经有了较多优质视频,这里作者就不再进行赘述了。
作者再对学习态度进行最后的说明,在学习的过程中,难免会遇到问题,这里的描述不太准确,在学习的过程中肯定会遇到问题,只有遇到问题才能证明我们在进步,但是遇到问题之后我们要如何做呢?作者认为能自己解决的问题尽可能自己解决,无论是查资料也好、百度也罢,最终解决问题的都是我们自己,会给我们留下极其深刻的印象,下一次遇到类似的问题后就会游刃有余。而实在解决不了时,可以咨询群里的老师或者寻求其他人的帮助,但问题解决后,一定要适当反思,寻找自己无法独立解决的原因。
前言到这里就结束了,下面,让我们一起迈进系统编程的大门吧。
第1部分 系统编程初探
第1章 系统编程初探
很高兴能和大家一起进入系统编程的世界。在本书的第一章中将对系统编程的基本概念进行引入,然后依次叙述系统编程的作用并进行一些相关的解释,旨在带领读者对系统编程有一个初次认知,从而更好地理解后续章节的内容。
1.1 什么是系统编程
本小节将以UCOS裸机实时操作系统为例对系统编程概念进行引入。相信学过裸机的同学一定不陌生,在使用UCOS实际开发一个项目或者一个产品的时候,会使用UCOS提供的接口进行编程,UCOS系统编程的一个简单示例代码如下所示:
void task(void *pd)
{
;
}
OS_STK stack[1024];
int main()
{
BspInit();
OSInit();
OSTaskCreate(task,(void *)0,&stack[1023],1);
OSStart();
}
上述代码会使用UCOS提供的函数对系统和硬件的相关功能进行调用,而系统编程的定义为:使用操作系统自身提供的接口进行开发的过程就叫做系统编程。
由于UCOS等一众裸机操作系统实现起来较为简单,所以系统的底层开发人员和上层应用开发人员是同一个人或者同一团队,而Linux操作系统将底层和上层应用进行了分离,从而可以让更多的人加入到Linux系统的开发中,但是这样每个人都会有权力调用底层接口对硬件进行操作,从而给整个系统带来了不稳定性和不安全性。为了解决这一隐患,Linux系统推出了系统调用。
所谓系统调用指的是Linux内核对一系列接口封装后提供给用户的一系列API函数,需要特定的机制才能使用系统调用,所以系统调用的出现,保证了系统的稳定性和不安全性。会在本章的第3小节对系统调用进行详细的讲解。
1.2 系统编程的作用
为了更直观的了解系统编程的作用,这里作者以武侠小说中练习绝世武功作为类比。若是想要练成一门绝世武功,只是有相对应的武功密集是不行的,在在之前,你需要通过每天扎马步、跑步等方式对身体进行锤炼,有了一个好的体魄才能开始武功秘籍的学习。这里要认清楚,并不是练了武功秘籍才有了好的体魄,而是有了好的体魄才能练习武功秘籍。
而系统编程就是后学开发的基础,系统编程基础打好之后可以完成的工作如下所示:
1)操作系统开发:系统编程是操作系统开发的基础,它涉及到开发和维护内核、驱动程序和其他底层系统软件。
2)应用程序开发:一些应用程序需要直接访问硬件或操作系统资源,例如音频、视频、网络或文件系统,因此需要进行系统编程。
3)系统管理:系统编程可以用于编写脚本和工具,以管理计算机系统和网络,例如配置文件、监控和自动化任务。
4)安全:系统编程可以用于编写安全软件和工具,例如防病毒软件、防火墙和入侵检测系统。
5)性能优化:系统编程可以用于编写优化代码,以提高程序的性能和响应速度,例如利用多线程、异步I/O和内存映射等技术。
1.3 系统调用和C语言库函数
学习前的提出的问题:
- 什么是系统调用?
- 什么是C语言库函数?
- 系统调用和C语言库函数之间有什么区别?
系统调用是操作系统提供给应用程序的一种接口,应用程序可以通过系统调用来请求操作系统提供某些服务或者完成某些操作,在操作系统内核中,系统调用通常由一组函数或者中断向量实现的,这些函数和中断向量用来接受自用户空间的请求,然后在内核空间中执行相应的操作,最后将结果返回给用户空间。
而C语言库函数是一组由C语言标准库提供的函数,这些函数封装了常见的操作和算法,例如字符串的处理、文件操作、数学运算、内存管理等等,可以帮助程序员更方便的完成任务,同时也提高了程序的可移植性。
应用程序、C语言库函数、系统调用以及内核接口API四者之间的关系如下所示:
应用程序位于用户空间,它包含了一些应用程序逻辑和C语言库函数。应用程序调用C语言库函数来完成一些常见的操作。当C语言库函数需要完成一些更底层的操作时,例如文件的读写、进程的创建等,它们需要调用系统调用。系统调用是一种特殊的函数调用,它将应用程序切换到内核空间,并执行相应的内核代码。
系统调用的接口由内核API提供,它们运行在内核空间中,可以访问系统的内存和硬件资源。系统调用将参数传递给内核API,并等待内核API的返回值。
应用程序、C语言库函数、系统调用和内核API之间通过函数调用的方式相互联系。系统调用是连接用户空间和内核空间的接口,内核API是实现系统调用功能的核心。
下面对系统调用和C语言库函数对比分析:
1)调用方式:库函数是在用户空间执行的,可以通过常规的函数调用方式来调用;而系统调用是在内核空间执行的,需要通过特定的中断或指令来调用,因此会有一定的开销。
2)访问权限:库函数是在用户空间执行的,只能访问应用程序的进程空间,不能直接访问系统资源(例如硬件、设备驱动等);而系统调用是在内核空间执行的,可以访问系统资源和硬件,能够提供更加底层和高级的功能。
3)功能范围:库函数通常提供一些常见的高级功能,例如字符串处理、文件操作、网络通信等;而系统调用提供的功能范围更加广泛,包括进程管理、内存管理、文件系统管理、网络通信等底层功能。
4)安全性:库函数通常是由应用程序开发人员编写和管理的,因此存在一定的安全风险,例如内存泄漏、缓冲区溢出等;而系统调用是由操作系统提供和管理的,能够提供更加安全和可靠的访问方式。
1.4 POSIX标准
学习前的疑问:
- 什么是POSIX标准?
- 人们为什么要指定POSIX标准?
UNIX作为一个开源的系统,任何一个人或者组织都可以根据UNIX发布自己的操作系统,这也就导致出现了一系列UNIX发行版,而每个发行版之间会存在差异、提供的API接口和库函数并不统一,所以在某一个发行版上成功运行的程序,并不一定能在其他发行版上运行,系统之间的移植极其不方便。
为了解决这个问题,IEEE(电气和电子工程师协会)于1988年发布了POSIX标准,即“可移植操作系统接口”,旨在提供一组公共的API和规范,使得不同的操作系统可以遵循相同的标准和接口。这使得开发人员可以编写一次代码,然后在多个不同的平台上运行,从而大大提高了应用程序的可移植性和开发效率。
POSIX标准包含了一系列操作系统接口和命令,主要包括以下几个方面:
方面 | 描述 |
系统调用 | 进程管理、文件操作、信号处理、进程间通信等方面的操作系统接口 |
库函数 | 标准C库函数和POSIX扩展库函数等 |
Shell命令 | 包括常用的命令行工具和工具箱,如文件管理、进程管理、网络管理等 |
环境变量 | 定义了一系列操作系统的环境变量,包括PATH、HOME、USER等 |
标准文件 | 定义了标准输入、输出和错误输出等文件描述符 |
另外,POSIX标准也可以提高应用程序的可靠性和安全性,因为它要求操作系统遵循一些最佳实践和安全措施,以确保操作系统的一致性和可靠性。这对于那些需要高可靠性和安全性的应用程序非常重要,例如军事、医疗、金融等领域的应用程序。
因此,POSIX标准的指定为开发人员提供了一组公共的、可移植的API和规范,使得他们可以更轻松地开发跨平台应用程序,并提高了应用程序的可靠性和安全性。
1.5 man命令
学习前的疑问:
- 为什么要学习man命令?
- man命令如何使用?
在 Linux 或 Unix 系统中,有大量的命令和函数可供使用,如果不知道具体用法,就很难发挥其最大的作用。通过 man 命令可以快速查找和学习命令和函数的用法,提高使用效率。
man 命令不仅提供了命令和函数的基本用法,还可以了解到更多的选项和参数,以及命令和函数的背景和特点。这样可以更深入地学习和理解系统知识,提高自己的技能水平。
打开虚拟机ubuntu终端并输入 “man” 命令,后跟要查询的命令或函数的名称。例如,要查看 ls 命令的手册,可以输入以下命令,进入指导界面之后如下图所示:
man ls
在最左侧会以不同的用语进行内容的区分,部分常用用语及其内容介绍如下图所示:
章节号 | 章节内容 | 章节号 | 章节内容 |
1 | 普通应用程序或者shell命令 | 2 | 系统调用 |
3 | 库函数 | 4 | 特殊文件 |
5 | 文件格式和协议 | 6 | 游戏程序 |
7 | 混杂设备 | 8 | 系统管理命令 |
9 | 非标准内核程序 |
如果要查看特定章节的手册,可以在命令后面加上章节号。常用的章节号有:
章节号 | 章节内容 | 章节号 | 章节内容 |
1 | 普通应用程序或者shell命令 | 2 | 系统调用 |
3 | 库函数 | 4 | 特殊文件 |
5 | 文件格式和协议 | 6 | 游戏程序 |
7 | 混杂设备 | 8 | 系统管理命令 |
9 | 非标准内核程序 |
一般常用的章节为1、2,如果不指定章节号,默认查看章节1的内容,以查看open命令系统调用部分的手册,可以输入以下命令,进入open指示界面之后如下图所示:
man 2 ope
在手册中可以使用箭头键(上下左右)进行滚动,也可以使用 PageUp 和 PageDown 键翻页。按下 Q 键可退出手册。
除了 man 命令外,还可以使用 info 命令和 --help 选项来查看命令和函数的帮助信息。其中 info 命令提供了比 man 命令更详细的信息(一般情况下man命令已经足够使用了,这里就不再讲解info命令了,有兴趣的同学可以自行了解一下),而 --help 选项通常提供了更简单的使用指南。
1.6 main函数传参
学习前提出的问题:
1. 什么是main函数传参
2.为什么要学习main函数传参?
本小节所用到的代码存放路径为“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\01_main”,如下图所示:
在 C 语言中,main 函数是程序的入口点。当程序被运行时,操作系统会首先调用 main 函数,并将命令行参数传递给它。main 函数的形式如下:
int main(int argc, char *argv[])
{
/* 代码 */
}
argc 英文全称为argument count,表示命令行参数的数量。
argv 英文全称为argument value,是一个指向字符串数组的指针,每个字符串表示一个命令行参数。
main 函数接收命令行参数这一功能在实际编程中非常有用。通过命令行参数,我们可以在运行程序时传递各种参数信息,这些信息可以用于控制程序的行为,或者提供必要的输入数据。以下是 main 函数传参的一些常见用途如下表所示:
用途 | 描述 |
控制程序行为 | 通过命令行参数控制程序的行为,如日志级别等 |
提供输入数据 | 通过命令行参数传递输入数据 |
程序调试 | 通过命令行参数传递测试数据、调试参数等 |
下面将通过一个实际的C程序对main函数传参进行举例。
举例:
首先进入到ubuntu的终端界面输入以下命令来创建demo01_main.c文件,如下图所示:
vim demo01_main.c
然后向该文件中添加以下内容,对main函数的两个参数进行简单的实验:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
/*argc表示传递参数的个数,程序自身也属于参数
argv表示传递参数的值,第一个参数值一定是程序名自身*/
printf("argc=%d\r\n",argc); //打印argc
printf("argv[0]=%s\r\n",argv[0]); //打印第一个参数值
printf("argv[1]=%s\r\n",argv[1]);//打印第二个参数值
return 0;
}
保存退出之后,使用以下命令进行编译,编译完成如下图所示:
gcc -o demo01_main demo01_main.c
首先使用命令“./demo01_main”来运行编译出来的程序,测试不加入参数直接运行的情况,运行结果如下图所示:
可以看到argc的参数值为1,即传递的参数只有1个,通过argv[0]的打印值可以得到传递的参数为程序自身名称,由于并没有传递第二个参数所以argv[1]为空,在这里显示为null。
然后输入命令“./demo01_main 12345”来运行程序,传递的参数为“12345”,运行结果如下图所示:
可以看到此时argc的参数值为2,证明传递了两个参数,argv[0]仍为程序自身名称,而argv[1]为我们所输入的 12345。
至此,对于main函数传参的讲解就结束了。