kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

Android 系统基本

http://tieba.baidu.com/p/2687199243?see_lz=1

二、锁定频率和核心

1. 了解热插拔

热插拔驱动(Hotplug Driver)是控制cpu负载控制核心上线下线的驱动

注意:所有热插拔驱动都是根据负载调节cpu上下线,只是策略有不同。这不是“高通异步专利”

高通机器默认热插拔:mpdecision
1
/system/bin/mpdecision

这个热插拔驱动其实工作的蛮不错的。各个厂商之间略会有不同。个人建议使用8064以后机器的不使用第三方的热插拔驱动

Exynos机器热插拔:pegasusq

三星的热插拔驱动是集成在了governor(调速器)中的,这个调速器可以看作ondemand+hotplug,工作方式为多核低频

Tegra机器热插拔:hotplug

两个字:渣渣
建议使用开发者开发的热插拔驱动

2. 如何锁定cpu核心

方法1: 使用kernel tuner

方法2: 使用脚本(只针对高通机器):

1
2
3
4
5
6
7
8
9
#!/system/bin/sh

stop mpdecision
echo 0 > /sys/devices/system/cpu/cpu1/online
chmod 444 /sys/devices/system/cpu/cpu1/online
echo 0 > /sys/devices/system/cpu/cpu2/online
chmod 444 /sys/devices/system/cpu/cpu2/online
echo 1 > /sys/devices/system/cpu/cpu3/online
chmod 444 /sys/devices/system/cpu/cpu3/online

注意: 这样做将没有热插拔驱动工作,在空载时依然会有两个核心上线

3. 如何锁定频率

(1)锁定cpu频率

步骤1: 将governor设置为performance

方法很简单,用fauxclock,trickester mod,kernel tuner都可以搞定,并且不占用资源采样负载

弊端:如果不修改温度配置文件,将会受到降频影响

步骤2: 修改权限让温控进程无法对其降频

1
2
3
4
5
6
7
8
9
#!/system/bin/sh
echo 你的cpu的最大频率 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
chmod 444 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
echo 你的cpu的最大频率 > /sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq
chmod 444 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
echo 你的cpu的最大频率 > /sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq
chmod 444 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
echo 你的cpu的最大频率 > /sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq
chmod 444 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq

注意:cpu频率以khz为单位,比如1728mhz应该在这里写为1728000

(2)锁定gpu频率

步骤1: 将governor设置为performance

方法很简单,用fauxclock,trickester mod,kernel tuner都可以搞定,并且不占用资源采样负载

弊端:如果不修改温度配置文件,将会受到降频影响

1
2
3
4
echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
echo performance > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor
echo performance > /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
echo performance > /sys/devices/system/cpu/cpu3/cpufreq/scaling_governor

步骤2:

1
2
echo 你的gpu的最大频率 > /sys/devices/platform/kgsl-3d0.0/kgsl/kgsl-3d0/max_gpuclk
chmod 444 /sys/devices/platform/kgsl-3d0.0/kgsl/kgsl-3d0/max_gpuclk

注意:gpu频率以hz为单位,比如400mhz应该在这里写为400000000

4. 关于锁定频率的看法

锁定频率对游戏性能很重要。根据我目前的结果来看,1.7g的krait 300似乎已经有点拖不动adreno 320了,而且随着频率降低,帧数跟着降低。但是对于日常使用来说,高频率只是一瞬间的事,并不需要多久,长期高频率对电池和发热的影响都会非常大。不推荐锁频,除非你要作性能测试

三、在调节linux设置

1. governor

(1) 什么是governor

governor大多数中文翻译为调速器,也叫调速策略。故名思议,根据cpu负载不同而如何决定提升或者降低频率靠的就是governor

(2) 为什么governor很重要

随着linux内核的更新,governor也会带来许多新功能来提升用户体验、响应速度、省电等。另外不同厂商对于不同governor的优化也是不同的。比如高通,对ondemand/msm-dcvs的优化非常好,然而对于小米用的interactive确实基本没怎么优化,在高通内核中的interactive非常之老旧,对于性能和省电都不利。在游戏中,htc的ondemand表现非常捉急,在需要提升频率的时候还按着不动,从而导致掉帧、顿卡等。切换到performance或者msm-dcvs会好不少。代表:riptide gp, asphalt 8,real racing 3

(3) 安卓上常见governor种类

cpu:

ondemand 故名思议,按需。ondemand根据cpu的负载来决定提升和降低频率,工作方式比较简单,也是最常见的一个governor

interactive 故名思议,交互。这个governor重点就是注重交互时的体验,它会比ondemand更快地提升到最高频率,而在降频时确实按照设定的时间慢慢地降。这么做会让系统很流畅,电量嘛,你懂的。

conservative 这个governor被开发者戏称为slow ondemand,它为了节电会限制cpu频率的提升,结果就是卡

performance 一直最高频

powersave 一直最低频

userspace 这个governor实质上就是让软件设定频率。比如在运行stability scaling test的时候,软件就会将其设为userspace

intellidemand intellidemand是faux123基于ondemand开发的一个governor,它和ondemand的主要区别就是在浏览网页的时候会限制频率,然后配合faux的热插拔驱动intelli-plug会获得比较好的省电效果

pegasusq 三星基于ondemand开发的热插拔governor

msm-dcvs msm(高通处理器前缀)-dcvs(dynamic clock & voltage scaling 动态频率电压调整) 这个governor是高通给krait架构开发的,具体有什么魔力我也不清楚,只是用它玩游戏的时候感觉比ondemand流畅多了

gpu:

ondemand 这个和cpu的是一样的,按需调整,根据负载决定频率

performance 永远最大频率

simple 这个governor是faux123对adreno 3xx开发的一个governor,其中参数有laziness和thresholds。前者数值分布1-10,决定的是忽略多少降频请求,数字越大性能和耗电都越高;后者是提升频率的阀值,即gpu达到多少负载提升频率,数值分布0-100,数字越大性能和耗电都越低

(3) 如何切换

最简单的当然是在fauxclock,trickester mod等软件里面切换

cpu:

1
2
3
4
echo 你的governor > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
echo 你的governor > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor
echo 你的governor > /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
echo 你的governor > /sys/devices/system/cpu/cpu3/cpufreq/scaling_governor

gpu:

1
echo 你的governor > /sys/devices/platform/kgsl-3d0.0/kgsl/kgsl-3d0/pwrscale/trustzone/governor

2. io scheduler

中文名:输入输出 调度器/io 调度器

(1) 为什么io scheduler很重要

io scheduler完全决定了磁盘的读写性能,而这对于用户体验的影响是极大打

(2) 安卓上常见io scheduler

cfq

completely-fair-quening

完全公平队列,是anticipatory模式的替代品,没有过多的做预测性调度,而是根据给定的进程io优先级,直接来分配操作的顺序。这个模式在linux上表现良好,但也许并不是最适合android的io调度模式,太强调均衡,而降低了连续读写数据的性能。

高通默认的就是这个,强烈建议改掉,根本不适合移动设备

noop

这个调度模式会把所有的数据请求直接合并到一个简单的队列里。不适合有机械结构的存储器,因为没有优化顺序,会增加额外的寻道时间。属于最简单的一个调度模式,无视io操作优先级和复杂性,执行完一个再执行一个,如果读写操作繁多的话,就会造成效率降低。

nvidia默认,有时候会造成顿卡,但是听说这个scheduler对省电比较有帮助

deadline

顾名思义,用过期时间来排序io操作顺序,保证先出现的io请求有最短的延迟时间,相对于写操作,给读操作更优先的级别。是比较好的一个调度模式。

性能不错

row

read over write

顾名思义,这个scheduler会优先处理读的请求。在移动设备上读的请求远远多于并且重要于写的请求,并且随机读取速度很重要。这个governor允许单或者双线程的读写,在同时有读写的情况下优先保证读,比较适合移动设备。

fiops

fair-iops 这个调度器虽然和cfq一样追求平均的优先级,但是是根据闪存设备重新设计的一个governor,各方面表现良好,是我列出来的五个scheduler里面性能最好的一个

如果有,强烈推荐fiops

sio

simple-io 在安卓上其实调度器越简单效果越好。sio就是最简单的一个调度器。不过还是有缺点的,就是随即读写性能不太好。在fiops出来以后,这个scheduler基本就被冷落了

3. read ahead buffer

这个其实奇怪。按理说缓存应该是越大越好,但是在安卓上好像不是这样,是越大越省电,越小系统越流畅,具体原理我也不懂。只列下方法

依旧,fauxclock,trickester mod等可以修改

命令:

emmc内置闪存:

1
echo 你想要的大小 > /sys/block/mmcblk0/quene/read_ahead_kb

sd卡:

1
echo 你想要的大小 > /sys/block/mmcblk1/quene/read_ahead_kb

默认为128k,如果想省电可以设成2048k

4. emmc entropy

entropy是一个叫混乱度的东西,好像是物理化学里面的,根据faux123的解释,闪存设备根本不需要entropy,所以就把它关掉来提高性能

fauxclock里面可以关闭

命令

1
2
echo 0 > /sys/block/mmcblk0/quene/add_random
echo 0 > /sys/block/mmcblk1/quene/add_random

5. c-states

高通从krait 200上引进,但是有bug,在krait 300上得到了修复

总共4个状态:

c0, wfi
c1, rentention
c2, standalone_power_collapse
c3, power_collapse

数字从低到高代表了睡眠程度的高低,数字越高的状态越省电

intel也有这个,haswell就是凭借着强大的c-states调整在tdp更高的情况下获得了更低的耗电和更长的续航。桌面上比如e3可以将c6状态打开,能在0.8v左右稳定在3.3g

高通的c-states和intel不一样,在平时工作的时候高通处理器进入c states的时间很少,主要集中在关屏深睡眠的时候

fauxclock可以打开,krait 300建议打开c0 c2 c3

命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
echo 1 > /sys/module/pm_8x60/modes/cpu0/wfi/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu1/wfi/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu2/wfi/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu3/wfi/idle_enabled
echo 0 > /sys/module/pm_8x60/modes/cpu0/retention/idle_enabled
echo 0 > /sys/module/pm_8x60/modes/cpu1/retention/idle_enabled
echo 0 > /sys/module/pm_8x60/modes/cpu2/retention/idle_enabled
echo 0 > /sys/module/pm_8x60/modes/cpu3/retention/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu0/standalone_power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu1/standalone_power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu2/standalone_power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu3/standalone_power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu0/power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu1/power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu2/power_collapse/idle_enabled
echo 1 > /sys/module/pm_8x60/modes/cpu3/power_collapse/idle_enabled

6. 不同的构图方式

从Android 4.0以后大家可以从build.prop里面发现这么几行:

1
2
debug.sf.hw=1
debug.composition.type=gpu

在4.2以后还可以看到这一行

1
persist.hwc.mdpcomp.enable=true

这就是构图方式

从谷歌4.2的build.prop的变化来看,谷歌已经开始强制使用mdp。性能更强但是耗电更低,何乐而不为

(1) 构图方式种类

cpu: 故名思议,cpu构图

gpu: gpu构图,在开发者选项中选择“关闭hw叠加层”和只设置debug.sf.hw=1都是让gpu构图

c2d: c2d构图,这个在2.x时代就已经被抛弃了,比gpu构图还烂

dyn: 这个似乎不错,但是所有高通机器的rom里面只有one的cm在用这个,而且开发者对这个构图方式的看法褒贬不一,就连这个选项是否生效都有争议。

mdp: 从firefox的开发者那里得知,新一点的机器都是有mdp管线的,比gpu构图性能更强、更省电。谷歌也因此强制使用这个构图方式

(2) 构图方式的影响

最常见的影响当然就是fps meter打开变卡了

firefox开发者的解释: https://bugzilla.mozilla.org/show_bug.cgi?id=911391

当叠加层数量低于mdp管线数量的时候,所有的构图都用mdp完成,不仅性能比gpu构图更好,而且还更省电。但是一旦叠加层数量超过mdp管线的数量,系统就会自动使用“部分mdp构图”,实质上就是要mdp和gpu合作构一帧的图。那么这个时候,就会导致性能下降

为什么打开一些overlay软件就变卡了呢?这就说明打开这类软件以后,比如fps meter,整个图层的数量已经超过了mdp的管线数量,系统启用gpu构图,导致系统、游戏流畅度下降。为什么有些人开始还不觉得fps meter对性能有影响呢?原因可能有三个:1. 他们还在4.2以下,还没用过mdp,一直都在用gpu构图;2. 他们一直都关掉了hw叠加层,也是一直用gpu构图,所以无法感知gpu构图对系统流畅度的严重影响;3. 他们打开了一些overlay软件,但是没有超过mdp的管线数量,没有进入gpu构图

构图的影响还不止这些,如果有人有one,可以试试把这一行

1
persist.hwc.mdpcomp.enable=true

从build.prop里面删掉

重启以后,反复按app抽屉的图标,对比与没删之前的流畅度。另外在贴吧等软件中,mdp构图也会增加滑动的流畅度。至于视频:1. 我没有高速摄像机;2. 这是非常容易感知的问题,耍赖不承认我是没办法的

mdp的缺点:

对于一些老的应用,mdp会造成负面影响,对流畅度负加成:比如在使用老版re管理器的时候,转移到多任务界面会有卡顿,而新版则非常流畅。 在叠加层数量超过mdp管线数量的时候,会转为“部分mdp构图”,mdp管线和gpu合作构图

不过谷歌已经强制使用mdp,随着软件更新,更快更省电的mdp构图将会逐渐替代gpu构图

四、关于作弊

很多厂商被逮着了“作弊”,其实我觉得根据不同的app调整策略不是坏事,但是你不开放给用户那就有问题了。凭什么只能跑分得到这样的待遇?厂商真的应该好好反思

1.作弊文件位置:

三星: TwDVFSApp.apk

HTC: /system/bin/pnpmgr; /system/etc/pnp.xml

NVIDIA:/system/app/NvCPLSvc.apk/res/raw/tegraprof.txt

2.如何对待?

作弊固然可耻,但是干掉这些东西又不是明智的选择。虽然这些文件有对跑分的专门配置和优化,但是它们还对普通应用程序/游戏有着配置。比如pnpmgr,它管理者省电模式、touch_boost、60fps视频cpu提频等等非常有用的调整;比如tegraprof,这里面更是有不少针对游戏优化的配置文件。关掉它们只会给用户体验减分。我希望所有厂商能够开放配置,让用户自由定制,而不是现在的加密处理。

五、关于测试的一些注意事项

  1. 注明机型,驱动版本,系统版本,内核类型(是官方还是第三方,编译器是什么。换一个编译器可以让某些性能差别达到20%)构图方式

  2. 不要在开启fps meter的同时打开其他悬窗监控软件。fps meter统计的是整个图层的平均帧数,开启其他悬窗监控软解无论刷新率调到多少都是不准的(除非overlay在fps meter上面)

  3. 测试的时候最好关掉温度进程,以防止意外降频

  4. 对比测试的时候注意变化量,在变化量超过一个的时候对比测试结果不可信

  5. 如果想反映整个游戏的帧数情况,用Adreno Profiler。在没有高速摄像机的情况下,这个比视频靠谱得多。https://developer.qualcomm.com/mobile-development/mobile-technologies/gaming-graphics-optimization-adreno/tools-and-resources

一、温度控制

很多人抱怨手机降频,其实这不是坏事,降频厉害,也是oem厂商所为,与soc厂商关系不是太大

可能抱怨最多的就是高通机器了,这里讲下高通机器的温度控制进程的基本调试

1. 开启和关闭温控进程

关闭:

1
stop thermald

开启:

1
start thermald
注意事项:

关闭温控以后,除非内核中也有温度保护,机器将不会降频,散热设计不好的机器很有可能因此烧毁。请谨慎考虑关闭温控进程

2. 降频阀值的调整

(1)了解自己手机的传感器

方法1:使用last_kmsg

1
adb pull /proc/last_kmsg

在adb目录下,找到last_kmsg文件,用记事本(推荐用notepad++/notepad2)打开,搜索sensor

方法2: 使用cat命令逐个查看

1
cat /sys/devices/virtual/thermald/thermald_zone*/temp

显示出的数值即该传感器的温度

毫无疑问,温度最高的那几个就是cpu温度传感器

(2)了解thermald配置文件

配置文件的路径在 /system/etc/thermald.conf,权限为644

注意:

对于大部分高通机器,打开即可编辑。对于HTC机器,这个文件是加密的,只能自己写。

对于三星的机器,这个文件会是一个软链,比如E330S软链到了thermald-8974.conf文件,那么你真正应该修改的文件则是thermald-8974.conf

(3)获取频率表

获取cpu频率表:

1
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies

获取gpu频率表:

1
cat /sys/devices/platform/kgsl-3d0.0/kgsl/kgsl-3d0/gpu_available_frequencies

注意: 部分三星机器,比如E330S无法查看gpu频率

(4)自己改写thermald.conf

步骤1: 了解thermald.conf的语言

1
2
3
4
5
6
7
8
9
10
11
sampling:采样时间
[tsen_tz_sensor*]:对于*号传感器的配置
thresholds:降频阀值,达到这个温度即降频
thresholds_clr:恢复阀值,达到这个温度即恢复到上一阶段配置的频率
actions:降频所采取的行动
cpu:降频cpu
gpu:降频gpu
shutdown:关机
lcd:改变屏幕亮度,+255最大
battery:不懂,但可以知道的是+1和+2,能降低温度
action_info:定义具体降频到多少

步骤2: 定义总采样时间

1
sampling 5000

数值越低采样越勤,也越耗费资源。不建议修改

步骤3: 定义传感器

1
2
3
4
5
6
[tsens_tz_sensor7]
sampling 1500
thresholds 54 57 64 70 75
thresholds_clr 51 54 57 64 70
actions gpu+cpu gpu+cpu cpu cpu cpu
action_info 400000000+1728000 320000000+1134000 1026000 918000 702000

步骤3.1:定义所需要的传感器

在你获得的传感器中,选择所需要的传感器。据我所知,绝大多数高通机器打sensor7, sensor8, sensor9都是cpu温度传感器,若要使用其他温度传感器,直接修改这个数字即可

步骤3.2:定义该传感器的采样时间

sampling 1500

数值越低,采样越勤,不建议修改

步骤3.3: 修改触发行为的温度阀值,即高于这个设定的温度就会采用当前定义的行为,比如降频

thresholds 54 57 64 70 75

步骤3.4: 修改回到上一行为的温度阀值,即低于这个设定温度就会回到上一个温度阀值所定义的行为(shutdown命令除外)

thresholds_clr 51 54 57 64 70

步骤3.5: 定义行为,最常见的就是cpu,gpu,shutdown,若要定义多个行为,则用加号相连

actions gpu+cpu gpu+cpu cpu cpu cpu

步骤3.6: 定义所采取的行为的具体数值,即降频降到多少。

action_info 400000000+1728000 320000000+1134000 1026000 918000 702000

注意: 其数值顺序必须与actions的顺序一模一样,最好与cpu和gpu频率表一致,否则容易出错。千万不要像三星官方一样敷衍了事。

3. 关于降频的看法

个人认为降频并不是一件坏事,在soc发热越来越大的今天,降频是厂商保证用户体验的一种方式之一:降低发热,降低耗电

但是我希望每个厂商都能像小米一样开发不同的模式,在需要降频省电的时候用一套温控配置,在需要性能的时候用另一套温控配置;而大多数国际厂商,比如三星,htc,nvidia,仅仅在跑分的时候使用了更高的温度配置,而且是用户无法选择的。这种行为应该表示抗议!强烈谴责!

c 文件锁flock

http://blog.csdn.net/lin_fs/article/details/7804494

头文件 #include<sys/file.h>

定义函数 int flock(int fd, int operation);

函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。

参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

返回值 返回0表示成功,若有错误则返回-1,错误代码存于errno。

flock只要在打开文件后,需要对文件读写之前flock一下就可以了,用完之后再flock一下,前面加锁,后面解锁。其实确实是这么简单,但是前段时间用的时候发现点问题,问题描述如下:

一个进程去打开文件,输入一个整数,然后上一把写锁(LOCK_EX),再输入一个整数将解锁(LOCK_UN),另一个进程打开同样一个文件,直接向文件中写数据,发现锁不起作用,能正常写入(我此时用的是超级用户)。google了一大圈发现flock不提供锁检查,也就是说在用flock之前需要用户自己去检查一下是否已经上了锁,说明白点就是读写文件之前用一下flock检查一下文件有没有上锁,如果上锁了flock将会阻塞在那里(An attempt to lock the file using one of these file descriptors may be denied by a lock that the calling process has already placed via another descriptor ),除非用了LOCK_NB。一个完整的用于测试的事例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//lockfile.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
	int fd,i;
	char path[] = "/home/taoyong/test.txt";
	extern int errno;
	fd = open(path,O_WRONLY|O_CREAT);
	if(fd != -1)
	{
		printf("open file %s ./n", path);
		printf("please input a number to lock the file./n");
		scanf("%d", &i);
		if (flock(fd, LOCK_EX) == 0)
		{
			printf("the file was locked./n");
		}
		else
		{
			printf("the file was not locked./n");
		}
		printf("please input a number to unlock the file./n");
		scanf("%d", &i);
		if (flock(fd, LOCK_UN)==0)
		{
			printf("the file was unlocked./n");
		}
		else
		{
			printf("the file was not unlocked./n");
		}
		close(fd);
	}
	else
	{
		printf("cannot open file %s/n", path);
		printf("errno:%d/n", errno);
		printf("errMsg:%s", strerror(errno));
	}
	return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//testprocess.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/file.h>

int main()
{
	int fd,i;
	char path[] = "/home/taoyong/test.txt";
	char s[] = "writing.../nwriting....../n";
	extern int errno;
	fd = open(path, O_WRONLY|O_CREAT|O_APPEND);
	if(fd!=-1)
	{
		printf("open file %s ./n",path);

		if (flock(fd,LOCK_EX|LOCK_NB) == 0)
		{
		      printf("the file was locked by the process./n");   
			if (-1 != write(fd,s,sizeof(s)))
			{
			      printf("write %s to the file %s/n", s, path);
			}
			else
			{
			      printf("cannot write the file %s/n", path);
			      printf("errno:%d/n", errno);
			      printf("errMsg:%s/n", strerror(errno));
			}       
			   
		}
		else
		{
		      printf("the file was locked by other process.Can't write.../n");
			printf("errno:%d:", errno);
		}
		close(fd);
	}
	else
	{
	  printf("cannot open file %s/n", path);
	      printf("errno:%d/n", errno);
	      printf("errMsg:%s", strerror(errno));
	}
	return 0;
}

debuginfo 编译速度

Have a look into /usr/lib/rpm/macros:

1
2
3
4
5
6
7
8
#       Compression type and level for source/binary package payloads.
#               "w9.gzdio"      gzip level 9 (default).
#               "w9.bzdio"      bzip2 level 9.
#               "w7.xzdio"      xz level 7, xz's default.
#               "w7.lzdio"      lzma-alone level 7, lzma's default
#
#%_source_payload       w9.gzdio
#%_binary_payload       w9.gzdio

binkernel.spec中加入

1
2
%_source_payload       w5.gzdio
%_binary_payload       w5.gzdio

略微降低压缩率,大大提高打包速度。kernel增加600K,debuginfo增加3M,时间从14分钟降至2分钟内

busybox

下载 http://www.busybox.net/downloads/binaries/latest/

busybox_armv7l

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
adb push ~/Download/busybox-armv7l /sdcard/busybox

adb shell
su
mount -o remount,rw /system

echo $PATH

cp /sdcard/busybox /system/xbin
chmod 755 busybox

# 但是每次前面都加上个busybox太麻烦了,所以我们还要继续完成安装。
# 在 /system/xbin 下输入
# busybox --install .
# 安装可能会影响root功能,建议改用ln几个常用即可。
# 例如PATH中/system/sbin在/system/bin之前,安装在/system/sbin之后root软件就访问不到/system/bin/su。
ln -s busybox free
ln -s busybox vi

CC_STACKPROTECTOR防内核堆栈溢出补丁分析

http://blog.aliyun.com/1126

内核堆栈溢出通常有两种情况。一种是函数调用栈超出了内核栈THREAD_SIZE的大小, 这是栈底越界,另一种是栈上缓冲越界访问,这是栈顶越界。

检测栈底越界

以arm平台为例,内核栈THREAD_SIZE为8K,当调用栈层次过多或某调用栈上分配过大的 空间,就会导致它越界。越界后struct thread_info结构可能被破坏,轻则内核 panic,重则内核数据被覆盖仍继续运行。

检测栈顶越界

对于栈顶越界,gcc提供了支持。打开内核配置CONFIG_CC_STACKPROTECTOR后,会打 开编译选项-fstack-protector.


CC_STACKPROTECT补丁是Tejun Heo在09年给主线kernel提交的一个用来防止内核堆栈溢出的补丁。默认的config是将这个选项关闭的,可以在编译内核的时候, 修改.config文件为CONFIG_CC_STACKPROTECTOR=y来启用。未来飞天内核可以将这个选项开启来防止利用内核stack溢出的0day攻击。这个补丁的防溢出原理是: 在进程启动的时候, 在每个buffer的后面放置一个预先设置好的stack canary,你可以把它理解成一个哨兵, 当buffer发生缓冲区溢出的时候, 肯定会破坏stack canary的值, 当stack canary的值被破坏的时候, 内核就会直接当机。那么是怎么判断stack canary被覆盖了呢? 其实这个事情是gcc来做的,内核在编译的时候给gcc加了个-fstack-protector参数, 我们先来研究下这个参数是做什么用的。

先写个简单的有溢出的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[wzt@localhost csaw]$ cat test.c

#include <stdio.h>
#include <stdlib.h>

void test(void)
{
	char buff[64];

	memset(buff, 0x41, 128);     //向64大小的buffer拷贝128字节, 肯定会发生缓冲区溢出。
}

int main(void)
{
	test();

	return 0;
}
1
2
3
[wzt@localhost csaw]$ gcc -o test test.c
[wzt@localhost csaw]$ ./test
段错误

反汇编看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[wzt@localhost csaw]$ objdump -d test > hex

08048384 <test>:
 8048384:       55                      push   %ebp
 8048385:       89 e5                   mov    %esp,%ebp
 8048387:       83 ec 58                sub    $0x58,%esp
 804838a:       c7 44 24 08 80 00 00    movl   $0x80,0x8(%esp)
 8048391:       00
 8048392:       c7 44 24 04 41 00 00    movl   $0x41,0x4(%esp)
 8048399:       00
 804839a:       8d 45 c0                lea    0xffffffc0(%ebp),%eax
 804839d:       89 04 24                mov    %eax,(%esp)
 80483a0:       e8 e3 fe ff ff          call   8048288 <memset@plt>
 80483a5:       c9                      leave
 80483a6:       c3                      ret

没什么特别的,我们在加上-fstack-protector参数看看:

1
2
3
4
[wzt@localhost csaw]$ gcc -o test test.c -fstack-protector
[wzt@localhost csaw]$ ./test
*** stack smashing detected ***: ./test terminated
已放弃

这次程序打印了一条堆栈被溢出的信息,然后就自动退出了。

在反汇编看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[wzt@localhost csaw]$ objdump -d test > hex1

080483d4 <test>:
 80483d4:       55                      push   %ebp
 80483d5:       89 e5                   mov    %esp,%ebp
 80483d7:       83 ec 68                sub    $0x68,%esp
 80483da:       65 a1 14 00 00 00       mov    %gs:0x14,%eax
 80483e0:       89 45 fc                mov    %eax,0xfffffffc(%ebp)
 80483e3:       31 c0                   xor    %eax,%eax
 80483e5:       c7 44 24 08 80 00 00    movl   $0x80,0x8(%esp)
 80483ec:       00
 80483ed:       c7 44 24 04 41 00 00    movl   $0x41,0x4(%esp)
 80483f4:       00
 80483f5:       8d 45 bc                lea    0xffffffbc(%ebp),%eax
 80483f8:       89 04 24                mov    %eax,(%esp)
 80483fb:       e8 cc fe ff ff          call   80482cc <memset@plt>
 8048400:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
 8048403:       65 33 05 14 00 00 00    xor    %gs:0x14,%eax
 804840a:       74 05                   je     8048411 <test+0x3d>
 804840c:       e8 db fe ff ff          call   80482ec <__stack_chk_fail@plt>
 8048411:       c9                      leave
 8048412:       c3                      ret

使用-fstack-protector参数后, gcc在函数的开头放置了几条汇编代码:

1
2
3
 80483d7:       83 ec 68                sub    $0x68,%esp
 80483da:       65 a1 14 00 00 00       mov    %gs:0x14,%eax
 80483e0:       89 45 fc                mov    %eax,0xfffffffc(%ebp)

将代码段gs偏移0×14内存处的值赋值给了ebp-4, 也就是第一个变量值的后面。

在call完memeset后,有如下汇编代码:

1
2
3
4
5
 80483fb:       e8 cc fe ff ff          call   80482cc <memset@plt>
 8048400:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
 8048403:       65 33 05 14 00 00 00    xor    %gs:0x14,%eax
 804840a:       74 05                   je     8048411 <test+0x3d>
 804840c:       e8 db fe ff ff          call   80482ec <__stack_chk_fail@plt>

在memset后,gcc要检查这个操作是否发生了堆栈溢出, 将保存在ebp-4的这个值与原来的值对比一下,如果不相同, 说明堆栈发生了溢出,那么就会执行stack_chk_fail这个函数, 这个函数是glibc实现的,打印出上面看到的信息, 然后进程退出。

从这个例子中我们可以看出gcc使用了-fstack-protector参数后,会自动检查堆栈是否发生了溢出, 但是有一个前提就是内核要给每个进程提前设置好一个检测值放置在%gs:0×14位置处,这个值称之为stack canary。所以我们可以看到防止堆栈溢出是由内核和gcc共同来完成的。

gcc的任务就是放置几条汇编代码, 然后和%gs:0×14位置处的值进行对比即可。 主要任务还是内核如何来设置stack canary, 也是CC_STACKPROTECTOR补丁要实现的目的, 下面我们仔细来看下这个补丁是如何实现的。

既然gcc硬性规定了stack canary必须在%gs的某个偏移位置处, 那么内核也必须按着这个规定来设置。

对于32位和64位内核, gs寄存器有着不同的功能。

64位内核gcc要求stack canary是放置在gs段的40偏移处, 并且gs寄存器在每cpu变量中是共享的,每cpu变量irq_stack_union的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arch/x86/include/asm/processor.h

union irq_stack_union {
	char irq_stack[IRQ_STACK_SIZE];
	/*
	 * GCC hardcodes the stack canary as %gs:40.  Since the
	 * irq_stack is the object at %gs:0, we reserve the bottom
	 * 48 bytes of the irq stack for the canary. 
	 */
	struct {
		char gs_base[40];
		unsigned long stack_canary;
	};
};

DECLARE_PER_CPU_FIRST(union irq_stack_union, irq_stack_union);

gs_base只是一个40字节的站位空间, stack_canary就紧挨其后。并且在应用程序进出内核的时候,内核会使用swapgs指令自动更换gs寄存器的内容。

32位下就稍微有点复杂了。由于某些处理器在加载不同的段寄存器时很慢, 所以内核使用fs段寄存器替换了gs寄存器。 但是gcc在使用-fstack-protector的时候, 还要用到gs段寄存器, 所以内核还要管理gs寄存器,我们要把CONFIG_X86_32_LAZY_GS选项关闭, gs也只在进程切换的时候才改变。 32位用每cpu变量stack_canary保存stack canary。

1
2
3
4
5
struct stack_canary {
	char __pad[20];         /* canary at %gs:20 */
	unsigned long canary;
};      
DECLARE_PER_CPU_ALIGNED(struct stack_canary, stack_canary);

内核是处于保护模式的, 因此gs寄存器就变成了保护模式下的段选子,在GDT表中也要有相应的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
diff --git a/arch/x86/include/asm/segment.h b/arch/x86/include/asm/segment.h
index 1dc1b51..14e0ed8 100644 (file)
--- a/arch/x86/include/asm/segment.h
+++ b/arch/x86/include/asm/segment.h
@@ -61,7 +61,7 @@
  *
  *  26 - ESPFIX small SS
  *  27 - per-cpu                       [ offset to per-cpu data area ]
- *  28 - unused
+ *  28 - stack_canary-20               [ for stack protector ]
  *  29 - unused
  *  30 - unused
  *  31 - TSS for double fault handler
@@ -95,6 +95,13 @@
 #define __KERNEL_PERCPU 0
 #endif

+#define GDT_ENTRY_STACK_CANARY         (GDT_ENTRY_KERNEL_BASE + 16)
+#ifdef CONFIG_CC_STACKPROTECTOR
+#define __KERNEL_STACK_CANARY          (GDT_ENTRY_STACK_CANARY * 8)
+#else
+#define __KERNEL_STACK_CANARY          0
+#endif
+
 #define GDT_ENTRY_DOUBLEFAULT_TSS      31

GDT表中的第28个表项用来定为stack canary所在的段。

1
2
#define GDT_STACK_CANARY_INIT                                           \
        [GDT_ENTRY_STACK_CANARY] = GDT_ENTRY_INIT(0x4090, 0, 0x18),

GDT_STACK_CANARY_INIT在刚进入保护模式的时候被调用, 这个段描述符项被设置为基地址为0, 段大小设为24,因为只在基地址为0, 偏移为0×14处放置一个4bytes的stack canary, 所以24字节正好。不理解的同学可以看看intel保护模式的手册, 对着段描述符结构一个个看就行了。

在进入保护模式后, start_kernel()会调用boot_init_stack_canary()来初始话一个stack canary。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*      
 * Initialize the stackprotector canary value.
 *
 * NOTE: this must only be called from functions that never return,
 * and it must always be inlined.
 */
static __always_inline void boot_init_stack_canary(void)
{
	u64 canary;
	u64 tsc;

#ifdef CONFIG_X86_64
	BUILD_BUG_ON(offsetof(union irq_stack_union, stack_canary) != 40);
#endif
	/*
	 * We both use the random pool and the current TSC as a source
	 * of randomness. The TSC only matters for very early init,
	 * there it already has some randomness on most systems. Later
	 * on during the bootup the random pool has true entropy too.
	 */
	get_random_bytes(&canary, sizeof(canary));
	tsc = __native_read_tsc();
	canary += tsc + (tsc << 32UL);

	current->stack_canary = canary;
#ifdef CONFIG_X86_64
	percpu_write(irq_stack_union.stack_canary, canary);
#else
	percpu_write(stack_canary.canary, canary);
#endif
}

随机出了一个值赋值给每cpu变量, 32位是stack_canary, 64位是irq_stack_union。

内核在进一步初始化cpu的时候,会调用setup_stack_canary_segment()来设置每个cpu的GDT的stack canary描述符项:

start_kernel()->setup_per_cpu_areas()->setup_stack_canary_segment:

1
2
3
4
5
6
7
8
9
10
11
12
static inline void setup_stack_canary_segment(int cpu)
{
#ifdef CONFIG_X86_32
	unsigned long canary = (unsigned long)&per_cpu(stack_canary, cpu);
	struct desc_struct *gdt_table = get_cpu_gdt_table(cpu);
	struct desc_struct desc;

	desc = gdt_table[GDT_ENTRY_STACK_CANARY];
	set_desc_base(&desc, canary);
	write_gdt_entry(gdt_table, GDT_ENTRY_STACK_CANARY, &desc, DESCTYPE_S);
#endif
}

在内核刚进入保护模式的时候, stack canary描述符的基地址被初始化为0, 现在在cpu初始化的时候要重新设置为每cpu变量stack_canary的地址, 而不是变量保存的值。通过这些设置当内核代码在访问%gs:0×14的时候, 就会访问stack canry保存的值。注意:setup_stack_canary_segment是针对32位内核做设置, 因为64位内核中的irq_stack_union是每cpu共享的, 不用针对每个cpu单独设置。 然后就可以调用switch_to_new_gdt(cpu);来加载GDT表和加载gs寄存器。

经过上述初始化过程,在内核代码里访问%gs:0×14就可以定位stack canary的值了, 那么每个进程的stack canary是什么时候设置的呢?

在内核启动一个进程的时候, 会把gs寄存器的值设为KERNEL_STACK_CANARY

1
2
3
4
5
6
7
8
9
10
--- a/arch/x86/kernel/process_32.c
+++ b/arch/x86/kernel/process_32.c
@@ -212,6 +212,7 @@ int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
	    regs.ds = __USER_DS;
	    regs.es = __USER_DS;
	    regs.fs = __KERNEL_PERCPU;
+       regs.gs = __KERNEL_STACK_CANARY;
	    regs.orig_ax = -1;
	    regs.ip = (unsigned long) kernel_thread_helper;
	    regs.cs = __KERNEL_CS | get_kernel_rpl();

内核在fork一个进程的时候, 有如下操作:

1
2
3
4
5
6
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
#ifdef CONFIG_CC_STACKPROTECTOR
	tsk->stack_canary = get_random_int();
#endif
}

随机初始化了一个stack_canary保存在task_struct结构中的stack_canary变量中。当进程在切换的时候, 通过switch宏把新进程的stack canary保存在每cpu变量stack_canary中, 当前进程的stack_canary也保存在一个每cpu变量中,完成stack canary的切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
diff --git a/arch/x86/include/asm/system.h b/arch/x86/include/asm/system.h
index 79b98e5..2692ee8 100644 (file)
--- a/arch/x86/include/asm/system.h
+++ b/arch/x86/include/asm/system.h
@@ -23,6 +23,22 @@ struct task_struct *__switch_to(struct task_struct *prev,

 #ifdef CONFIG_X86_32

+#ifdef CONFIG_CC_STACKPROTECTOR
+#define __switch_canary                                                \
+       "movl "__percpu_arg([current_task])",%%ebx\n\t"                 \
+       "movl %P[task_canary](%%ebx),%%ebx\n\t"                         \
+       "movl %%ebx,"__percpu_arg([stack_canary])"\n\t"
+#define __switch_canary_oparam                                         \
+       , [stack_canary] "=m" (per_cpu_var(stack_canary))
+#define __switch_canary_iparam                                         \
+       , [current_task] "m" (per_cpu_var(current_task))                \
+       , [task_canary] "i" (offsetof(struct task_struct, stack_canary))
+#else  /* CC_STACKPROTECTOR */
+#define __switch_canary
+#define __switch_canary_oparam
+#define __switch_canary_iparam
+#endif /* CC_STACKPROTECTOR */
+
 /*
  * Saving eflags is important. It switches not only IOPL between tasks,
  * it also protects other tasks from NT leaking through sysenter etc.
@@ -46,6 +62,7 @@ do {                                                  \
	                 "pushl %[next_ip]\n\t"     /* restore EIP   */     \
	                 "jmp __switch_to\n"        /* regparm call  */     \
	                 "1:\t"                                             \
+                    __switch_canary                                    \
	                 "popl %%ebp\n\t"           /* restore EBP   */     \
	                 "popfl\n"                  /* restore flags */     \
	                                                                    \
@@ -58,6 +75,8 @@ do {                                                  \
	                   "=b" (ebx), "=c" (ecx), "=d" (edx),              \
	                   "=S" (esi), "=D" (edi)                           \
	                                                                    \
+                      __switch_canary_oparam                           \
+                                                                       \
	                   /* input parameters: */                          \
	                 : [next_sp]  "m" (next->thread.sp),                \
	                   [next_ip]  "m" (next->thread.ip),                \
@@ -66,6 +85,8 @@ do {                                                  \
	                   [prev]     "a" (prev),                           \
	                   [next]     "d" (next)                            \
	                                                                    \
+                      __switch_canary_iparam                           \
+                                                                       \
	                 : /* reloaded segment registers */                 \
	                    "memory");                                      \
 } while (0)

前面讲过当gcc检测到堆栈溢出的时候, 会调用glibc的stack_chk_fail函数, 但是当内核堆栈发生溢出的时候,不能调用glibc的函数,所以内核自己实现了一个stack_chk_fail函数:

kernel/panic.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef CONFIG_CC_STACKPROTECTOR

/*
 * Called when gcc's -fstack-protector feature is used, and
 * gcc detects corruption of the on-stack canary value
 */
void __stack_chk_fail(void)
{
	panic("stack-protector: Kernel stack is corrupted in: %p\n",
		 __builtin_return_address(0));
}
EXPORT_SYMBOL(__stack_chk_fail);

#endif

当内核堆栈发生溢出的时候,就会执行stack_chk_fail函数, 内核当机。

这就是这个补丁的原理,不懂的同学请参考:

http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=commitdiff;h=60a5317ff0f42dd313094b88f809f63041568b08