嵌入式系统的启动速度因设备的性能和代码的质量而异,但整体而言,从消费者的角度考虑,系统的启动速度确定是越快越好。所以,对嵌入式系统进行性能优化,加快设备的启动时间为项目后期必须进行的一项工做。须要注意的是:嵌入式Linux设备的优化不是一蹴而就的,而是一个不断优化,不断改进的过程。linux
现将本身掌握的嵌入式设备的性能优化策略进行总结,若有不对的地方,还望批评指正。程序员
设备启动的快慢目前尚未一个统一的标准。在项目中通常按照客户的标准。web
对于开发人员来讲,评价设备的性能通常是经过在代码中增长log的方式。这种方式具备如下几点优势:算法
精确度高。shell
一般可以精确到毫秒。有特殊需求的状况下,能够精确到毫秒,好比使用gettimeofday函数。缓存
灵活性强。性能优化
能够测出代码中任意部分的代码运行所耗费的时间。多线程
在嵌入式设备中,导入设备启动时间过长,性能低下的缘由通常包括以下几个方面:app
硬件的缘由异步
硬件的缘由通常指的是设备的CPU及Flash性能。若是代码的运算量很大,碍于CPU和Flash的性能,会致使CPU过于繁忙。有些设备碍于成本的缘由,Flash过小,不少东西都须要压缩存放,那么在设备启动过程当中,解压也须要必定的时间。
程序的缘由
代码须要进行大量的IO操做,好比读写文件,内存访问等等,CPU更多的时候处于等待状态。而有些代码,因为编写的缘由,导师各个进程之间相互等待,CPU利用率低下,制约了设备的性能。
优化并不能盲目的优化,盲目追求性能,还要统筹考虑。通常要遵循如下原则:
等效性原则
优化先后的代码实现的功能要彻底一致
有效性原则
优化后的代码必定要比原先的代码运行速度快活着占用存储空间小,或者两者兼有,不然就是毫无心义的优化
经济性原则
不少代码性能低下的部分缘由也是因为硬件性能的限制,好比将文件压缩存放以节约存储成本。优化要在现有的条件下考虑,不要以更换存储空间的大小来换取解压的时间。优化要付出较小的代价,不少程序员在作优化的时候,抱怨设备的性能有限,要求提升设备的性能,这样只能是本末倒置。
此处提出的优化的方法主要是从代码的角度考虑,不包括升级硬件。
绝大多数的嵌入式设备都会使用busybox做为实现Linux命令的工具,所以BusyBox提供了一个比较完善的环境,能够适用于任何小的嵌入式系统。
BusyBox 是一个集成了一百多个最经常使用linux命令和工具的软件。BusyBox 包含了一些简单的工具,例如ls、cat和echo等等,还包含了一些更大、更复杂的工具,例grep、find、mount以及telnet。有些人将BusyBox称为Linux工具里的瑞士军刀。简单的说BusyBox就好像是个大工具箱,它集成压缩了Linux的许多工具和命令,也包含了Android系统的自带的shell。
BusyBox包含三种类型的命令:
APPLET
即为人所熟知的applets,它由BusyBox建立一个子进程,而后调用exec执行相应的功能,在执行完毕后,返回控制给父进程。
APPLET_NOEXEC
系统将调用fork建立子进程,而后执行BusyBox中相应的功能,在执行完毕后,返回控制给父进程。
APPLET_NOFORK
它至关于builts-in,只是执行BusyBox的内部函数,没必要建立子进程,因此其效率极高。
众所周知,在Linux中调用fork,exec是很耗费时间的,因此咱们应该尽量的使用APPLET_NOFORK命令,其次是APPLET_NOEXEC,最后是APPLET。
在BusyBox1.9中,属于APPLET_NOFORK的功能有:
basename,cat,dirname,echo,false,hostid,length,logname,mkdir,pwd,rm,rmdir,deq,sleep,sync,touch,true,usleep,whoami,yes
属于APPLET_NOEXEC的功能有:
awk,chgrp,chmod,chown,cp,cut,dd,find,hexdump,ln,soort,test,xargs......
因此优化shell脚本的策略通常有:
1. 去掉无用的脚本 2. 尽量的使用BusyBox内部的命令 3. 尽可能不要使用管道pipe 4. 减小管道中的命令数目 5. 尽可能不要使用·
进程的启动过程以下:
1 搜索其所依赖的动态库 2 加载动态库 3 初始化动态库 4 初始化进程 5 将程序的控制权移交给main函数
要加快的进程的启动速度,能够从如下几方面入手:
1 减小加载的动态库的数量
a) 使用dlopen,将启动时不须要的动态库延后加载 b) 将一些动态库改成静态库 优势: - 减小了加载动态库的数量 - 在与其余动态库合并以后,动态库内部的函数之间没必要再进行动态连接、符号查找,从而提升速度 缺点: - 该动态库若是被多个动态库或进程所依赖的话,那么该动态库将被复制多份合并到新的动态库中,致使总体的文件大小增长,占用更多的Flash。 - 失去了动态库原有的代码段内存共享,所以可能会致使内存使用上的增长
2 优化加载动态库时的搜索路径
a) 设置LD_HWCAP_MASK,禁掉一些不用的硬件特性 b) 将全部的动态库都放在一个目录下,而且将目录放在LD_LIBRARY_PATH的开始 c) 不能放在一个目录的,在进程中加入-rpath选项,指定搜索路径
若是作了以前的工做仍然没法知足进程启动速度的要求,那就从进程的调度上下功夫,能够:
进程改成线程
能够把原来的进程分割为两个部分:
常驻内存部分:其为daemon进程,主要负责加载进程所须要的动态库,侦听用户信号,建立和销毁用户逻辑线程
完成用户逻辑部分: 由daemon部分建立线程,按用户需求完成用户逻辑
这样就节省掉了加载动态库、初始化动态库和全局变量部分,能够缩短进程的响应时间,来知足用户的需求
还能够再引伸一下,将原来的多个daemon进程的常驻内存部分进行合并,根据用户逻辑需求,建立不一样的进程。
优势:
建立线程时,不须要从新加载动态库,故缩短了进程的响应时间
多个业务逻辑共享动态库时,避免了系统为每一个业务逻辑建立动态库的数据段,从而节省了大量的内存。
缺点:
由原来的进程改成线程,工做量比较大,代码修改上存在必定的风险
多个业务逻辑线程之间共享动态库时,有可能会带来全局变量的冲突
因为仍是存在daemon进程部分,因此其堆栈内存不会被释放,多个业务逻辑线程所存在内存泄露会纠缠在一块儿,从而使问题更加复杂。
preload进程
在进程的main函数中插入一行语句:
pause();
这样,当进程启动时,加载完动态库后,就会停在这里,不会运行用户逻辑。
当咱们须要相应用户时,向该进程发送一个信号,这样用户就会继续前进,处理用户逻辑,这样就节省了进程加载动态库的过程。
这里须要一个信号处理函数:
当用户逻辑执行完成后,就退出进程,同时再启动该进程,这是进程会在加载完动态库后,停留在那里
void sigCont( int unused)
{
return;
}
int main(int argc, char** argv)
{
signal(SIGCONT,sigCont);
pause();
}
提早加载,延后退出
当进程启动须要较长时间时,不少程序员仅仅想到了将其提早加载(在开机时启动),却没有想到起退出条件,而致使进程中又多了一个daemon进程。 所以提早加载,延后退出须要更加精确的控制进程的生命周期。
调整CPU频率
if表达式
从左到右对表达式求值,当结果肯定后也就不在须要计算其余的表达式,也就是常说的“短路”机制,所以对于if语句能够作如下优化:
循环语句的优化
#将分支语句提到循环的外面的例子
for (i=nloop; i>0; i--)
{
if(n == 1)
j += 2;
else
j += 1;
}
#改成:
if (n == 1)
{
for (i=nloop; i>0; i--)
{
j += 2;
}
}
else
{
for (i=nloop; i>0; i--)
{
j += 1;
}
}
#############################################################################################################
# 展开循环语句的例子
#方式1
for (n = 0; n < 1024*1024; n++)
{
n++;
}
#方式2
for (n = 0; n < 1024*512; n++)
{
n++;
n++;
}
#方式3
for (n = 0; n < 1024*256; n++)
{
n++;
n++;
n++;
n++;
}
#以上三种方法,方式三所用的时间最短,效率最高
寄存器的使用遵循ATPCS标准
ATPCS标准是嵌入式开发应尽可能遵循的标准,主要内容以下:
子程序间经过寄存器R0——R3来传递参数。
被调用的子程序在返回前无需恢复寄存器R0——R3的内容
在子程序中,使用寄存器R4——R11来保存局部变量。
若是在子程序中使用了寄存器R4——R11的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则没必要进行这些操做。
R12用做子程序间scratch寄存器,记做ip。
在子程序间的链接代码段常用这些规则
R13用做数据栈指针,记做sp。
在子程序间寄存器R13不能用做其余用途。
R14成为链接寄存器,记做lr。
它用来保存子程序的返回地址。
R15是程序计数器,记做pc。
子程序返回结果为一个32位整数时,能够经过寄存器R0返回;结果为一个64位整数时,能够经过寄存器R0和R1返回,以此类推。
函数参数优化
函数的参数最好不超过4个
4个如下的形参能够经过寄存器来传递,4个以上的参数,则须要经过栈来传递。
同事若是参数小于4个,R0-R4中剩余的寄存器能够保存函数中的局部变量。
减小局部变量的个数
当函数内部寄存器变量多于12个时,并不意味着只是将前面的12个临时变量分配寄存器,以后的临时变量都是经过栈内存来操做。
线程的优化
内存操做的优化
内存访问流程
+ CPU试图访问一块内存
+ CPU首先确认该内存是否已经被加载到cache中
+ 若是加载到cache中,则直接在cache中定位
+ 若是未加载到cache中,则经过CPU和内存直接的地址总线,向内存发送地址的高27位地址
+ 当内存收到高27位地址后,利用SDRAM的突发交换模式,将连续的32个字节传送给CPU的cache,填充一个缓存行
+ CPU能够经过地址的高27位来定位cache的缓存行,利用地址的低5位定位到缓存行中具体的字节
调整进程的优先级