OuyangSheng's Blog

Thinking,Tech....


  • Home

  • Categories

  • Archives

  • Tags

  • Search

Linux GPIO-Pinctrl Subsystem

Posted on 2018-06-08 | In Linux |
Words count in article 1,183 | Reading time 5

前言

对于一个IO Port来讲,有两方面需要配置,一是本身的功能设定,二是如果作为GPIO的话输入输出的设定。前者由Pin Controller控制,后者由GPIO Controller控制。

Pin Controller控制包括:

  • 引脚功能配置。例如该I/O pin是一个普通的GPIO还是一些特殊功能引脚(例如memeory bank上CS信号)。
  • 引脚特性配置。例如pull-up/down电阻的设定,drive-strength的设定等。

GPIO Controller控制包括:

  • 配置GPIO的方向
  • 如果是输出,可以配置high level或者low level
  • 如果是输入,可以获取GPIO引脚上的电平状态

软件架构

linux kernel中的GPIO subsystem则用两个软件模块来对应上面两类硬件功能:

  • pin control subsystem的模块图

底层的pin controller driver是硬件相关的模组,初始化的时候会向pin control core模块注册pin control设备(通过pinctrl_register这个bootom level interface)。pin control core模块是一个硬件无关模块,它抽象了所有pin controller的硬件特性,仅仅从用户(各个driver就是pin control subsystem的用户)角度给出了top level的接口函数,这样,各个driver不需要关注pin controller的底层硬件相关的内容。

  • GPIO subsystem的模块图

基本上这个软件框架图和pin control subsystem是一样的,其软件抽象的思想也是一样的,当然其内部具体的实现不一样。

后对照Allwinner H5 Linux3.10的实际代码看,Pinctrl和Gpio是整合到一起的,传统gpio的调用最终还是到Pinctrl下面的API来实现。整体框架如下:

1. 普通Driver如何调用pinctrl

上图从上往下看,普通Driver调用pin control subsystem的主要目标是:1.设定该设备的功能复用;2. 设定该device对应的那些pin的电气特性。以emmc模块驱动(sunxi-mmc.c)为例:
DTS中Emmc部分的配置为:

1
2
3
4
5
6
sdc0: sdmmc@01c0f000 {
compatible = "allwinner,sun50i-sdmmc0";
pinctrl-names = "default","sleep";
pinctrl-0 = <&sdc0_pins_a>;
pinctrl-1 = <&sdc0_pins_b>;
};

Pinctrl部分的DTS配置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
r_pio: pinctrl@01f02c00 {
compatible = "allwinner,sun50i-r-pinctrl";

sdc0_pins_a: sdc0@0 {
allwinner,pins = "PF0", "PF1", "PF2", "PF3","PF4","PF5";
allwinner,function = "sdc0";
allwinner,muxsel = <2>;
allwinner,drive = <1>;
allwinner,pull = <1>;
};

sdc0_pins_b: sdc0@1 {
allwinner,pins = "PF0", "PF1", "PF2", "PF3","PF4","PF5";
allwinner,function = "io_disabled";
allwinner,muxsel = <7>;
allwinner,drive = <1>;
allwinner,pull = <1>;
};

其中pinctrl中有两种状态,default是默认状态,从上面dts配置可以看出配置为sdc0的Function,sleep是休眠时或是emmc关闭时调用的状态,从上可看出配置为io_disabled状态。
代码中调用的流程:

1
2
3
4
5
6
7
8
host->pinctrl = devm_pinctrl_get(&pdev->dev);      
--- 1. 获取设备(设备模型中的struct device)的pin control state holder(struct pinctrl)
host->pins_default = pinctrl_lookup_state(host->pinctrl,PINCTRL_STATE_DEFAULT);
-- 2a. 根据state name在pin control state holder找到对应的pin control state, 这里是default
host->pins_sleep = pinctrl_lookup_state(host->pinctrl,PINCTRL_STATE_SLEEP);
-- 2b. 这里是sleep状态
ret = pinctrl_select_state(host->pinctrl , host->pins_sleep); -- 3a. 休眠时或是emmc关闭时调用
rval = pinctrl_select_state(host->pinctrl, host->pins_default); -- 3b. 打开时调用

2. 板级Pinctrl driver如何注册

pinctrl driver根据pin controller的实际情况,实现struct pinctrl_desc(包括pin/pin group的抽象,function的抽象,pinconf、pinmux的operation API实现,dt_node_to_map的实现,等等),并注册到kernel中。
代码流程–>pinctrl-sun50iw1p1.c开始:

1
2
3
4
5
6
7
8
sun50iw1p1_pinctrl_probe 
--> sunxi_pinctrl_init(pdev,&sun50iw1p1_pinctrl_data); -- 其中sun50iw1p1_pinctrl_data描述了所有IO的Function及irq状况
-->
pctrl_desc->confops = &sunxi_pconf_ops; -- 操作函数是用来配置引脚的特性,如pull-up/down,driver strength
pctrl_desc->pctlops = &sunxi_pctrl_ops; -- dts 功能group的相关解析
pctrl_desc->pmxops = &sunxi_pmx_ops; -- 功能复用enable/disable,gpio方向
-->
pctl->pctl_dev = pinctrl_register(pctrl_desc,&pdev->dev, pctl); -- 注册到pin control subsystem

3. Gpio如何关联

GPIO的HW block应该和其他功能复用的block是对等关系的,它们共同输入到一个复用器block,这个block的寄存器控制哪一个功能电路目前是active的。

代码中,sunxi_pinctrl_init(pdev,&sun50iw1p1_pinctrl_data)–>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pctl->chip->request = sunxi_pinctrl_gpio_request,
pctl->chip->free = sunxi_pinctrl_gpio_free,
pctl->chip->direction_input = sunxi_pinctrl_gpio_direction_input,
pctl->chip->direction_output = sunxi_pinctrl_gpio_direction_output,
pctl->chip->get = sunxi_pinctrl_gpio_get,
pctl->chip->set = sunxi_pinctrl_gpio_set,
pctl->chip->set_debounce = sunxi_pinctrl_gpio_set_debounce,
pctl->chip->of_xlate = sunxi_pinctrl_gpio_of_xlate,
pctl->chip->to_irq = sunxi_pinctrl_gpio_to_irq,

ret = gpiochip_add(pctl->chip);
if (ret)
goto pinctrl_error;

for (i = 0; i < pctl->desc->npins; i++) {
const struct sunxi_desc_pin *pin = pctl->desc->pins + i;

ret = gpiochip_add_pin_range(pctl->chip, dev_name(&pdev->dev),
pin->pin.number - pctl->desc->pin_base,
pin->pin.number, 1);
if (ret)
goto gpiochip_error;
}

通过gpiochip_add(gpiolib.c)将 pctl->chip中指定的api与gpio子系统关联起来,以gpiod_direction_output为例:

1
2
3
4
5
6
7
8
9
gpiod_direction_output ----> 
chip->direction_output(chip, offset, value) ---->
pctl->chip->direction_output = sunxi_pinctrl_gpio_direction_output ---->
pinctrl_gpio_direction_output(chip->base + offset) ---->
pinctrl_gpio_direction(gpio, false) ---->
pinmux_gpio_direction(pctldev, range, pin, input) ---->
ops->gpio_set_direction(pctldev, range, pin, input) ---->
sunxi_pmx_gpio_set_direction ----> pinmux_ops里面的callback,所以最终回到pinctrl driver
sunxi_pmx_set(pctldev, offset, desc->muxval, true) -- 最终控制 寄存器的地方

以上只是根据allwinner H5实际代码大略分析,具体可以参考以下。

refs

Linux GOIO子系统-wowo

Linux clock subsystem

Posted on 2018-06-07 | In Linux |
Words count in article 1,201 | Reading time 5

前言

Linux Clock的框架图:

以上的框架图就能说明整个clock子系统的运作情况,从上往下看,clock consumer – clock core – clock provider,同时通过DTS来配置串接。大多数Linux子系统中都呈现这种分层式架构。

clock consumer :向其它driver提供操作clocks的通用API。
clock core:实现clock控制的通用逻辑,这部分和硬件无关。
clock provider:将和硬件相关的clock控制逻辑封装成操作函数集,交由底层的platform开发者实现,由通用逻辑调用。

硬件相关

那么板子上clock相关模块到底是如何分布和规划的?

上图从左往右看,最开始的源头产生:Oscillator –> 中间可能有倍频PLL、分频Divider、多路选择Mux、开关Gate –> clock使用的硬件模块。

也就是说,软件层面的架构 与 硬件层面的架构有一定程度的吻合。比如软件需要提供:1.Enable/Disable Clk;2. Set clk rate; 3. 选择clock的parent。

软件架构

主要是如何体现clock consumer <---> clock core(CCF) <---> clock provider。两个层面问题,一是clock provider是如何注册进CCF的,二是clock consumer如何从CCF通过相应API获取对应的Clock控制。

1.Clock provider注册进CCF

以Allwinner H5 Linux3.10为例,初始化的起点从start_kernel() 进入板级DT_MACHINE_START,如下:
linux-3.10\arch\arm\mach-sunxi\Sun50i.c

1
2
3
4
5
DT_MACHINE_START(SUNXI_DT, CONFIG_SUNXI_SOC_NAME)
.map_io = sunxi_map_io,
.init_time = sunxi_timer_init,
.dt_compat = sunxi_board_dt_compat,
MACHINE_END

从sunxi_timer_init – > of_clk_init –> __clk_of_table中所有需要注册的各种类型clock – > linux-3.10\drivers\clk\sunxi\clk-sun50iw2.c 中CLK_OF_DECLARE定义的初始化函数。

以上有两个问题阐释下,一是__clk_of_table从何而来,二是具体初始化哪些clock类型。
__clk_of_table是从所有CLK_OF_DECLARE定义而来,而初始化哪些clock则是由CLK_OF_DECLARE中的定义与Clock Provider DTS中的定义匹配而来,举个Fixed Clock的例子:
clk-sun50iw2.c中定义CLK_OF_DECLARE(sunxi_fixed_clk, "allwinner,fixed-clock",of_sunxi_fixed_clk_setup); ,而DTS(sun50iw1p1-clk.dtsi)中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* register fixed rate clock*/
clk_losc: losc {
#clock-cells = <0>;
compatible = "allwinner,fixed-clock";
clock-frequency = <32768>;
clock-output-names = "losc";
};

clk_hosc: hosc {
#clock-cells = <0>;
compatible = "allwinner,fixed-clock";
clock-frequency = <24000000>;
clock-output-names = "hosc";
};

从上面可以看到是通过allwinner,fixed-clock字段来匹配的,匹配之后最终通过clk_register/of_clk_add_provider添加到CCF中以供Clock Consumer使用。这只是其中一种clock类型,那么还有哪些类型呢?
根据clock的特点,clock framework将clock分为fixed rate、gate、devider、mux、fixed factor、composite六类,每一类clock都有相似的功能、相似的控制方式,因而可以使用相同的逻辑,统一处理,这充分体现了面向对象的思想。

  • fixed rate clock:具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock。
  • gate clock:只可开关,会提供.enable/.disable回调。
  • divider clock:可以设置分频值,因而会提供.recalc_rate/.set_rate/.round_rate回调。
  • mux clock:可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调。
  • fixed factor clock:具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。
  • composite clock:顾名思义,就是mux、divider、gate等clock的组合。
2.Clock Consumer如何使用Clock

以Emmc模块为例,首先需要在DTS中EMMC相关部分配置,指明需要消费哪些Clock Provider中提供clock类型:

1
2
3
4
5
6
7
sdc2: sdmmc@01C11000 {
compatible = "allwinner,sun50i-sdmmc2";
~~~~~~
clocks = <&clk_hosc>,<&clk_pll_periph1x2>,<&clk_sdmmc2_mod>,<&clk_sdmmc2_bus>,<&clk_sdmmc2_rst>;
clock-names = "osc24m","pll_periph","mmc","ahb","rst";
~~~~~~
};

如以上的clk_sdmmc2_mod、clk_sdmmc2_bus、clk_sdmmc2_rst就有在sun50iw1p1-clk.dtsi中定义。然后在emmc驱动相关代码中通过CCF提供的API取得相关clk并配置,如下(sunxi-mmc.c):

1
2
3
4
5
6
7
8
9
10
11
12
host->clk_mmc = devm_clk_get(&pdev->dev, "mmc");  -- 取得clk
if (IS_ERR(host->clk_mmc)) {
dev_err(&pdev->dev, "Could not get mmc clock\n");
ret = PTR_ERR(host->clk_mmc);
goto error_disable_regulator;
}

ret = clk_prepare_enable(host->clk_mmc); -- 使能clk
if (ret) {
dev_err(&pdev->dev, "Enable mmc clk err %d\n", ret);
goto error_disable_clk_ahb;
}

除了devm_clk_get/clk_prepare_enable外还有一些clock操作的API:

  • clk_enable/clk_disable,启动/停止clock。不会睡眠。
  • clk_prepare/clk_unprepare,启动clock前的准备工作/停止clock后的善后工作。可能会睡眠。
  • clk_get_rate/clk_set_rate/clk_round_rate,clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误。如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值。
  • 获取/选择clock的parent clock。
  • clk_prepare_enable,将clk_prepare和clk_enable组合起来,一起调用。clk_disable_unprepare,将clk_disable和clk_unprepare组合起来,一起调用。

更详细的解读可参考如下。

ref

Linux common clock framework(1)_概述
Common Clock Framework系统结构

Linux process

Posted on 2018-05-17 | In Linux |
Words count in article 2,579 | Reading time 9

进程

Linux内核中进程用task_struct结构体表示,称为进程描述符,该结构体相对比较复杂,有几百行代码,记载着该进程相关的所有信息,比如进程地址空间,进程状态,打开的文件等。对内核而言,进程或者线程都称为任务task。内核将所有进程放入一个双向循环链表结构的任务列表(task list)。

Linux内核是抢占式多任务工作模式,进程大致分为两类(两者可相互转化):

  • 守护进程(服务): daemon,由内核在系统引导过程中启动的进程,和终端无关进程;
  • 前台进程:跟终端相关,通过终端启动的进程(用户进程);

按进程占用资源的多少可以讲进程分为:

  • CPU-Bound: CPU密集型(对CPU密集型是对cpu占用率高的进程),非交互;
  • IO-Bound: IO密集型(等待I/O时间长的进程),交互;

进程的状态

  • TASK_RUNNING
    运行态: running
    就绪态: ready(可以运行但是没运行)

  • TASK_INTERRUPTIBLE & TASK_UNINTERRUPTIBLE

在linux系统中,一个进程无法获得某种资源,如锁(自旋锁、互斥锁、顺序锁、信号量等)、信号、中断,将进入等待状态,同时一个进程也可以根据需要主动进入等待状态。将进程从运行状态迁移到等待状态的方式:

  1. wait_event
  2. wait_event_timeout
  3. wait_event_interruptible
  4. wait_event_interruptible_timeout

1和2函数将进程放人等待队列中,并将当前进程的状态设置为TASK_UNINTERRUPTIBLE,即在等待队列中的进程不可以被信号激活,而只能由中断事件激活;
3和4函数将进程放人等待队列中,并将当前进程的状态设置为TASK_INTERRUPTIBLE,即在等待队列中的进程可以被信号和中断事件激活;
2和4函数会为当前等待进程设置一个定时器,当等待进程在指定的时间内没有被信号或者中断激活时,这个定时器将激活等待进程。

  • TASK_STOPPED
    进程被停止执行,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态。

  • EXIT_ZOMBIE
    进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时进程成为僵尸进程。

  • EXIT_DEAD
    进程的最终状态。

创建新进程

分为三类:

  • Linux进程创建
  • Linux用户级线程创建
  • Linux内核线程创建

Linux进程创建

通过fork()及exec()系统调用创建进程。

  • fork: 采用复制当前进程的方式来创建子进程,此时子进程与父进程的区别仅在于pid, ppid以及资源统计量(比如挂起的信号)。

  • exec:读取可执行文件并载入地址空间执行;一般称之为exec函数族,有一系列exec开头的函数,比如execl, execve等。

fork过程复制资源包括代码段,数据段,堆,栈。fork调用者所在进程便是父进程,新创建的进程便是子进程;在fork调用结束,从内核返回两次,一次继续执行父进程,一次进入执行子进程。

进程内存段:

exec执行的例子(ls)

Linux用户级线程创建

通过pthread库中的pthread_create()创建线程,也并非”轻量级进程”,在Linux看来线程是一种进程间共享资源的方式,线程可看做是跟其他进程共享资源的进程。

fork, vfork,clone根据不同参数调用do_fork:

  • pthread_create: flags参数为 CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND
  • fork: flags参数为 SIGCHLD
  • vfork: flags参数为 CLONE_VFORK, CLONE_VM, SIGCHLD

所以进程与线程最大的区别在于资源是否共享,线程间共享的资源主要包括内存地址空间,文件系统,已打开文件,信号等信息, 如下图蓝色部分的flags便是线程创建过程所必需的参数。

Linux内核线程创建

通过kthread_create()创建内核线程,最初线程是停止的,需要使用wake_up_process启动它。它没有独立的地址空间,即mm指向NULL。这样的线程只在内核运行,不会切换到用户空间。所有内核线程都是由kthreadd作为内核线程的祖师爷,衍生而来的。

Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。内核需要多个执行流并行,为了防止可能的阻塞,支持多线程是必要的。内核线程就是内核的分身,一个分身可以处理一件特定事情。内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,它与内核中的其他进程”并行”执行。内核线程经常被称之为内核守护进程。

内核线程主要有两种类型:

  1. 线程启动后一直等待,直至内核请求线程执行某一特定操作。

  2. 线程启动后按周期性间隔运行,检测特定资源的使用,在用量超出或低于预置的限制时采取行动。

总结

Linux使用task_struct来描述进程和线程:

一个进程由于其运行空间的不同, 从而有内核线程和用户进程的区分, 内核线程运行在内核空间, 之所以称之为线程是因为它没有虚拟地址空间, 只能访问内核的代码和数据, 而用户进程则运行在用户空间, 不能直接访问内核的数据但是可以通过中断, 系统调用等方式从用户态陷入内核态,但是内核态只是进程的一种状态, 与内核线程有本质区别。

用户进程运行在用户空间上, 而一些通过共享资源实现的一组进程我们称之为线程组, Linux下内核其实本质上没有线程的概念, Linux下线程其实上是与其他进程共享某些资源的进程而已。但是我们习惯上还是称他们为线程或者轻量级进程。

因此, Linux上进程分3种,内核线程(或者叫核心进程)、用户进程、用户线程, 当然如果更严谨的,你也可以认为用户进程和用户线程都是用户进程。

  • 内核线程拥有 进程描述符、PID、进程正文段、核心堆栈

  • 用户进程拥有 进程描述符、PID、进程正文段、核心堆栈 、用户空间的数据段和堆栈

  • 用户线程拥有 进程描述符、PID、进程正文段、核心堆栈,同父进程共享用户空间的数据段和堆栈

进程调度

现在的操作系统都是多任务的,为了能让更多的任务能同时在系统上更好的运行,需要一个管理程序来管理计算机上同时运行的各个任务(也就是进程)。

这个管理程序就是调度程序,它的功能说起来很简单:

  1. 决定哪些进程运行,哪些进程等待;

  2. 决定每个进程运行多长时间;

此外,为了获得更好的用户体验,运行中的进程还可以立即被其他更紧急的进程打断。总之,调度是一个平衡的过程。一方面,它要保证各个运行的进程能够最大限度的使用CPU(即尽量少的切换进程,进程切换过多,CPU的时间会浪费在切换上);另一方面,保证各个进程能公平的使用CPU(即防止一个进程长时间独占CPU的情况)。

把进程区分为三类:

类型 描述 示例
交互式进程(interactive process) 此类进程经常与用户进行交互, 因此需要花费很多时间等待键盘和鼠标操作. 当接受了用户的输入后, 进程必须很快被唤醒, 否则用户会感觉系统反应迟钝 shell, 文本编辑程序和图形应用程序
批处理进程(batch process) 此类进程不必与用户交互, 因此经常在后台运行. 因为这样的进程不必很快相应, 因此常受到调度程序的怠慢 程序语言的编译程序, 数据库搜索引擎以及科学计算
实时进程(real-time process) 这些进程由很强的调度需要, 这样的进程绝不会被低优先级的进程阻塞. 并且他们的响应时间要尽可能的短 视频音频应用程序, 机器人控制程序以及从物理传感器上收集数据的程序

实时进程:实时进程的优先级是静态设定的,而且始终大于普通进程的优先级。因此只有当runqueue中没有实时进程的情况下,普通进程才能够获得调度。实时进程采用两种调度策略,SCHED_FIFO 和 SCHED_RR,FIFO 采用先进先出的策略,对于所有相同优先级的进程,最先进入runqueue的进程总能优先获得调度;Round Robin采用更加公平的轮转策略,使得相同优先级的实时进程能够轮流获得调度。

调度算法的主要演化:O(n) -> O(1) -> CFS

观看以下两个video:
Scheduling in Linux: O(n), O(1) Scheduler
Completely Fair Scheduling (CFS)

进程相关命令

pstree, ps, pidof,pgrep, top, htop, glance, pmap, vmstat, dstat, kill,pkill, job, bg, fg, nohup

进程通信机制

  • 同一主机上
  1. signal(信号)
  2. shm: shared memory(分享内存)
  3. semophore:信号量,一种计数器
  • 不同主机上
  1. rpc: remote procedure call(远程过程调用)
  2. socket(套接字): IP和端口号

references

Linux内核的整体架构

Linux进程管理与调度-之-目录导航

Linux进程管理-gityuan

Linux内核进程管理架构图

Android开机启动优化

Posted on 2018-05-14 | In Android |
Words count in article 2,345 | Reading time 10

开机流程

如下图:

Linux优化

可以通过添加打印module init的log,来check每个module初始化时的时间。从而找到花费时间比较多的module:

1
2
3
4
5
6
7
8
9
10
11
12
--- a/init/main.c
+++ b/init/main.c
@@ -785,7 +785,7 @@ int __init_or_module
do_one_initcall(initcall_t fn)
if (initcall_blacklisted(fn))
return
-EPERM;

- if (initcall_debug)
+ if (1)
ret =
do_one_initcall_debug(fn);

优化方案:

  1. 通过一个比gzip更快的方式去解压内核镜像;
  2. 去掉系统中一些不必要的log打印;
  3. 去掉一些系统中不需要的驱动模块;
  4. 启动时即以最大频率(cpu/DDR)且多核一起跑;

Android优化

查看时间:

adb logcat -v threadtime -b events > logcat_envents.txt

adb logcat -v threadtime > logcat.txt

具体到全志H5平台,查看LOG发现:

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
01-01 03:00:23.233  1503  1503 I auditd  : type=2000 audit(0.0:1): initialized

01-01 03:00:24.730 1503 1503 I auditd : type=1403 audit(0.0:2): policy loaded auid=4294967295 ses=4294967295

01-01 03:00:31.590 1520 1520 I boot_progress_start: 8846 //systemclock.uptimemillis(),开机到当前时间,毫秒。

//Zygote 进程preload 开始时间 32bit zygote

01-01 03:00:34.168 1520 1520 I boot_progress_preload_start: 11425

//Zygote 进程preload 结束时间32bit zygot

01-01 03:00:37.394 1520 1520 I boot_progress_preload_end: 14650

//System server 开始运行时间

01-01 03:00:37.780 1964 1964 I boot_progress_system_run: 15037

//Package Scan 开始

01-01 03:00:38.553 1964 1964 I boot_progress_pms_start: 15810

//System 目录开始scan

01-01 03:00:38.931 1964 1964 I boot_progress_pms_system_scan_start: 16188

//data 目录开始scan

01-01 03:00:42.210 1964 1964 I boot_progress_pms_data_scan_start: 19467

//package scan 结束时间

01-01 03:00:42.230 1964 1964 I boot_progress_pms_scan_end: 19487

//package manager ready

01-01 03:00:42.727 1964 1964 I boot_progress_pms_ready: 19984

//Activity manager ready,这个事件之后便会启动home Activity。

01-01 03:00:45.432 1964 1964 I boot_progress_ams_ready: 22689

//HomeActivity 启动完毕,系统将检查目前所有的window是否画完,如果所有的window(包括wallpaper, Keyguard 等)都已经画好,系统会设置属性service.bootanim.exit值为1.并且trigger下面的event。

01-01 03:00:49.393 1964 2003 I boot_progress_enable_screen: 26650

//SF设置service.bootanim.exit属性值为1,标志系统要结束开机动画了,可以用来跟踪开机动画结尾部分消耗的时间

01-01 03:00:49.545 1525 1527 I sf_stop_bootanim: 26801
01-01 03:00:49.579 1964 2020 I wm_boot_animation_done: 26836

// 打开Launcher APP
01-01 03:00:50.516 1964 2429 I am_create_activity: [0,14996279,5,tv.lfstrm.smotreshka_launcher/tv.lfstrm.mediaapp_launcher.MainActivity,android.intent.action.MAIN,NULL,NULL,268435712]

用bootchart 图形化显示Android启动过程,如图

定制本地服务

Init程序的log信息位于kernel Log中,通过检索“init starting”,我们可以找到init进程启动了哪些本地服务,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[    5.632951] init: Starting service 'logd-reinit'...
[ 5.635827] init: Starting service 'zygote'...
[ 5.637221] init: Starting service 'netd'...
[ 5.643302] init: Starting service 'healthd'...
[ 5.644695] init: Starting service 'lmkd'...
[ 5.646006] init: Starting service 'servicemanager'...
[ 5.647547] init: Starting service 'surfaceflinger'...
[ 5.752483] init: Starting service 'console'...
[ 6.154863] init: Starting service 'audioserver'...
[ 6.156551] init: Starting service 'cameraserver'...
[ 6.158068] init: Starting service 'displayd'...
[ 6.160115] init: Starting service 'drm'...
[ 6.161186] init: Starting service 'gpio'...
[ 6.162746] init: Starting service 'installd'...
[ 6.164462] init: Starting service 'keystore'...
[ 6.165992] init: Starting service 'mediacodec'...
[ 6.167700] init: Starting service 'mediadrm'...
[ 6.169339] init: Starting service 'mediaextractor'...
[ 6.171560] init: Starting service 'media'...
[ 6.172651] init: Starting service 'multi_ir'...
[ 6.174182] init: Starting service 'ril-daemon'...
[ 6.175224] init: Starting service 'systemmix'...
[ 6.399317] init: Starting service 'adbd'...
[ 6.733571] init: Starting service 'bootanim'...

Init进程解析init.rc及init.xxx.rc之类的文件,启动一些本地服务,如果我们的设备中没有电话模块、蓝牙模块,我们可以将这些没用的本地服务在init.rc里注释掉。笔者做了对比,去掉几个本地服务与没有去掉本地服务,二者在开机时间上几乎没有减少多少,这也可以理解,因为本地服务就是几个程序,少执行和多执行几个程序对于总体开机时间没有多大影响,不过,去掉没有使用的本地服务,对整个系统性能来说,会有微不足道的提升。

优化建议:

  1. 去掉开机动画服务(service bootanim /system/bin/bootanimation)可以一定程度上提高系统的启动速度。
  2. 可以通过在execute_one_command函数中统计测量 ,比如大于100ms的命令打印出来,再分析定位原因,这里命令执行时间长基本算BUG。

preloaded classes & resources

全志H5所花费时间:

1
2
3
4
5
01-01 03:00:12.869  1520  1520 I Zygote  : Preloading classes...
01-01 03:00:14.982 1520 1520 I Zygote : ...preloaded 4162 classes in 2113ms.
01-01 03:00:15.683 1520 1520 I Zygote : Preloading resources...
01-01 03:00:15.879 1520 1520 I Zygote : ...preloaded 114 resources in 196ms.
01-01 03:00:15.887 1520 1520 I Zygote : ...preloaded 41 resources in 8ms.

Android系统为了提高应用程序的启动速度,会在Zygote进程初始化过程中加载一些常用的java class和资源文件到进程的内存中,从而共享常用的class和resourse资源。

preloaded-classes list(frameworks\base\preloaded-classes)中预加载的类位于dalvik zygote进程的heap中。在zygote衍生一个新的dalvik进程后,新进程只需加载heap中没有预加载的类(这些后加载进来的类成为该进程所private独有的),这样便加快了应用程序的启动速度。实际上这是一种以空间换时间的办法,因为几乎没有一个应用程序能够使用到所有的预加载类,必定有很多类对于该应用程序来说是冗余的。但是也正如Google所说,智能手机开机远没有启动应用程序频繁——用户开机一次,但直到下次再开机之前可能要运行多个应用程序。因此牺牲一点启动时间来换取应用程序加载时的较快速度是合算的。

preloaded-classes list已经是Google Android工程师使用众多测试工具分析,加以手动微调后形成的最优化预加载列表,涵盖了智能机上最长见的应用类型所需要的各种类。很难想象我们自己能够有什么手段能够获得比这样更优的一个预加载列表。所以,除非你的Android系统是被移植到非智能手机设备上使用(例如MID、EBOOK,可以不需要Telephony相关的类),不建议去“优化”preloaded-classes list。

优化建议:

  1. preloadClasses()与preloadResources()可以放到两个线程里面跑。
  2. 修改zygote的nice值,及thread priority。

定制Android系统服务

由Android的启动过程可知,init进程启动了app_process作为zygote,在app_process里启动了Dalvik虚拟机,然后加载执行了第一个Java程序ZygoteInit作为Dalvik主线程,在ZygoteInit里fork了第一个Java程序SystemServer,在SystemServer里启动了大量的Android的核心服务,通常来说这些服务一般不要去动,如果我们的设备里没有使用过某些服务,并且将来也明确不使用,可以将其去掉。

SystemServer启动了哪些Android服务:

1
2
3
4
5
6
7
8
9
10
11
PowerManagerService:电源管理服务  
ActivityManagerService:最核心服务之一,Activity管理服务
PackageManagerService:程序包管理服务
WindowManagerService:窗口管理服务
BluetoothService:蓝牙服务
WifiP2pService:Wifi点对点直联服务
WifiService:WIFI服务
ConnectivityService:网络连接状态服务
MountService:磁盘加载服务,通常也mountd和vold服务结合
AudioService:AudioFlinger上层的封装的音量控制管理服务
UsbService:USB Host和device管理服务

优化这些services其实就是剔除我们不需要的一些services,而且不仅仅是修改SystemServer.java的问题,任何使用到被优化剔除掉的服务的代码都必须加以修改,否则系统肯定是起不来的。这样工作量大,而且难度也不小,并且有一定风险。因此对这些services的优化要慎之又慎。

PackageManagerService扫描、检查APK安装包信息

PMS对/system/framework,/system/app,/data/app,/data/app-private目录中的APK扫描耗费了大量的时间,如果预置的三方应用很多,这样启动的时间就会越长。

优化建议:

  1. /system/app下的应用,如果是预置应用,在Android.mk建议加上LOCAL_DEX_PREOPT := true控制,在/system/vendor下的预置应用,如果此应用编译时间比较长的,也使用上LOCAL_DEX_PREOPT := true

  2. 尽量减少data区内置app的数量,这个会严重影响开机速度,特别是第一次的开机速度。放在system的app 尽量生成odex 这样会加快开机速度。

Readahead

因为IO慢的原因(cpu与存储类设备如emmc通讯),有两段耗时的地方:1. Zygote的preload 资源和class;2. PackageManagerService的包扫描。可以采用Linux上使用较多的readahead机制,大概原理是:

  1. 统计开机过程中,读取的块数据信息,记录下来保存;

  2. 再次开机,通过记录下来的块数据读取信息,直接起一个服务,预先开始读,zygote或packagemanagerservice要读文件的时候,文件数据已经在cache中了。

这样主要IO时间,跑到readahead进程去了。

reference:

深入浅出 - Android系统移植与平台开发(六)- 为Android启动加速

Android开机速度优化简单回顾——readahead

android 5.1.1开机优化(framework层)

Linux性能调优指南

Embeded Linux kernel boot process

Posted on 2018-05-05 | In Linux |
Words count in article 1,262 | Reading time 5

第一阶段(从跳转kernel image到跑到start_kernel之前)

kernel入口地址在ENTRY(stext),代码在arch/arm/kernel/head.S。

如何体现的?

在arch/arm/kernel/vmlinux.lds.S中,同时参考编译后生成的System.map文件,System.map是内核的内核符号表,在这里可以找到函数地址,变量地址,包括一些链接过程中的地址定义等等。

其中要做的事情:

  • 设置为SVC模式,关闭所有中断。

为什么要设置成SVC模式?

除了用户模式之外的其他6种处理器模式称为特权模式。特权模式下,程序可以访问所有的系统资源(除了特定模式下的影子寄存器),也可以任意地进行处理器模式的切换,特权模式中,除系统模式外,其他5种模式又称为异常模式。
而用户模式下访问的资源受限,故不能使用用户模式,系统模式的优先级低于异常模式,故不使用系统模式,快中断模式、中断模式、中止模式、未定义模式用于特殊场景下由CPU自动切入,故不使用,所以需要使用SVC模式。

为什么要关闭所有中断?

在启动过程中,中断环境并没有完全准备好,也就是中断向量表和中断处理函数并没有完成设置,一旦有中断产生,可能会导致预想不到的问题,或者是程序跑飞。因此,在准备好中断环境之前,需要关闭所有中断。

  • 获取CPU ID,提取相应的proc info

这里存在的MMU标识,也就是我们需要在打开MMU之前需要先获取procinfo的原因,因为打开MMU之前需要配置临时内核页表,而配置临时内核页表需要这里的MMU标识来进行设置。

  • 验证tags或者dtb

dtb里面存放了各种硬件信息,如果dtb有问题,会导致后续开机过程中读取的设备信息有问题而导致无法开机。
在生成dtb的时候会在头部上添加一个幻数magic,而验证dtb是否合法主要也就是看这个dtb的magic是否和预期的值一致。

  • 创建临时内核页表的页表项

为了打开MMU,内核需要创建一个临时内核页表,用于kenrel启动过程中的打开MMU的过渡阶段。 在打开MMU的过程中,CPU还是按照地址顺序一条接着一条去获取指令,也就是说此时PC指针还是指向这段代码区域的物理地址。
当MMU打开之后,如果没有恒等映射的话,PC指针此时还是指向这段代码区域的物理地址,但实际上CPU会把PC指针的地址当作虚拟地址进行操作,而造成找不到对应的物理地址。

  • 使能MMU

  • 跳转到start_kernel,也就是跳转到第二阶段,看其中寄存器说明,存的什么值

    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
    /*
    * The following fragment of code is executed with the MMU on in MMU mode,
    * and uses absolute addresses; this is not position independent.
    *
    * r0 = cp#15 control register
    * r1 = machine ID
    * r2 = atags/dtb pointer
    * r9 = processor ID
    */
    __INIT
    __mmap_switched:
    adr r3, __mmap_switched_data

    ldmia r3!, {r4, r5, r6, r7}
    cmp r4, r5 @ Copy data segment if needed
    1: cmpne r5, r6
    ldrne fp, [r4], #4
    strne fp, [r5], #4
    bne 1b

    mov fp, #0 @ Clear BSS (and zero fp)
    1: cmp r6, r7
    strcc fp, [r6],#4
    bcc 1b

    ARM( ldmia r3, {r4, r5, r6, r7, sp})
    THUMB( ldmia r3, {r4, r5, r6, r7} )
    THUMB( ldr sp, [r3, #16] )
    str r9, [r4] @ Save processor ID
    str r1, [r5] @ Save machine type
    str r2, [r6] @ Save atags pointer
    bic r4, r0, #CR_A @ Clear 'A' bit
    stmia r7, {r0, r4} @ Save control register values
    b start_kernel
    ENDPROC(__mmap_switched)

第二阶段 - start_kernel

以下只剖析个大概,不对细节做太深入的探讨。

代码路径:init/main.c 从start_kernel()开始,挑重点的介绍:

重要函数 作用
setup_arch architecture-specific setup ,如arch\arm\mach-sunxi\Sun8i.c 中MACHINE_START(SUNXI, “sun8i”) 下相关
mm_init memory management
sched_init 调度相关
vfs_caches_init file system, including kernfs, sysfs, rootfs, mount tree
rest_init 道生一(start_kernel–>cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先。可以通过ps命令,查看PID及PPID的关系。

其中大部分的工作是在rest_init中完成,对于Android系统来讲init进程在 system/core/init/init.c实现,下图代表Android系统的boot process:

设备驱动的init及probe也是在其中实现的,如下面的调用栈:

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
`--> rest_init
|
`--> kernel_init
|
|--> kernel_init_freeable
| |
| |--> do_basic_setup
| | |
| | `--> do_initcalls
| | :
| | :--> early init calls
| | :--> core init calls
| | :--> post core init calls
| | :--> arch init calls
| | : :
| | : `--> customize_machine
| | : |
| | : `--> machine_desc->init_machine
| | :
| | :--> subsys init calls
| | :--> fs init calls
| | :--> device init calls
| | : :
| | : `--> module_init() entries
| | : drivers are probed during driver registration
| | :
| | `--> late init calls

从以上也可以看出kernel的启动顺序。


References:

kernel 启动流程

深入淺出 start_kernel()

谈谈 U-boot 启动流程

Posted on 2018-04-25 | In uboot |
Words count in article 2,112 | Reading time 8

前面的话

很自然的疑问,uboot是干什么的?它内部的运行机制是什么?把这两个问题解释清楚也不容易,所谓‘费曼法则’:通过向别人清楚的解说一件事情,来确认自己真的弄懂了这件事。

写技术文章,想深入浅出,非大师(在这个领域有多年的积累和实践)不可为,很显然我没有,本文只是初浅的梳理,大多是资料的索引。目的是熟悉整个框架,出问题时方便debug。

以RK3399为例。

一切得从官方的README开始,其中有对uboot完整的描述,从文件目录介绍、代码流程、编译等等各方面都有涉及,建议通读一遍。

然后是Rockchip官方 opensource ,其中有开源代码链接,及boot flow介绍。

也许看完以上资料,可能还是懵懵懂懂,或许网上还有关于其他平台更为详细的资料介绍呢?search一番,发现samsung S5PV210文档最多,官方资料最详尽,是ARMV7架构,未加入Arm trust Firmware。

简而言之,uboot是在操作系统内核运行之前运行,可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。

下面,从一些基础讲起,然后到网上分析比较多的Samsung S5PV210 Uboot,最后到RK3399的Uboot相关。

基础

  • Text、Data、BSS、Heap、Stack的区别

先来看一幅图:

简单来说,Text是存放代码的,Data是存放已经初始化的全局变量,BSS是未初始化或是0的全局变量,Heap是由malloc等分配的堆,Stack是函数的局部变量或是函数返回值。

对于Uboot来讲,要特别关注链接脚本arch\arm\cpu\u-boot.lds,其中各段的分配情况,及代码重定位。

  • NorFlash、eMMC、Nand flash、SRAM、SDRAM的区别
存储器 上电后访问状态 掉电后存储器中数据状态
Nor 可以读取数据,无法直接写入 数据存在
Nand /eMMC 初始化后才能读取写入 数据存在
SRAM 可以读取写入数据 数据不存在
SDRAM 上电后没有初始化DDR控制器无法进行数据读写 数据不存在

那么以上存储器在uboot启动过程中都扮演什么角色呢?

Norflash作为arm处理器程序存储器。可以试想一下,如果程序存储器掉电以后里面的数据没有了。那么你的电脑如何自启动,难道每次开机前都要重新烧写一次代码。在此处可以思考一个问题,在上电后norflash可以看作一个可以随机读取的只读存储器。但是我们运行的程序,一般情况下.text段(代码段)是只读(ok),.rodata(只读数据段)是只读(也ok)。那么问题来了,对于.data段(数据段)和.bss段(未初始化的全局变量和静态变量)在程序运行的过程中变量的值是需要改变的(改变一个变量的值在底层硬件完成操作<在相应的地址(变量在物理地址上存储地址)上写入数据>),很可惜Norflash只能直接读取无法直接进行写操作。(重要! 怎么解决这个问题? 这时就需要SRAM 因为SRAM上电后就可以直接去读写,下面我就解释下SRAM的功能和作用。

SRAM特性:掉电易失(失去电源供电后SRAM里存储的数据不存在了),可以随意<读写>数据。(容量小,程序运行速度快,价格高,一般在SoC里面。).在实际运行时,SRAM可以作为c语言运行时的堆栈空间。把arm处理器的sp(堆栈指针寄存器)设置在sram中,为c语言运行提供环境。关于全局变量的问题,我单独提一下,uboot在重定位前(将uboot镜像从flash搬运到ddr中继续运行前),无论是汇编还是c程序中没有定义全局变量。只是定义了一个结构体指针gd_t *gd_ptr用于存储uboot镜像的长度,重定位地址等信息,将gd_ptr的地址存储在r9中,r9中存储的地址值为sram顶端减去一个sizeof(gd_t )。(存储在sram里就可以随意读写了嘛 后面分析uboot代码时我会详细讲解)。

SDRAM特性:掉电易失(失去电源供电后SDRAM里存储的数据不存在了),上电后没有初始化DDR控制器,无法进行数据读写。既然需要使用大容量的SDRAM,必须配置ddr时钟和ddr控制器的寄存器。这一步在哪完成呢?(思考一下) 没错就是在norflash和SRAM搭建的程序运行环境中完成。完成什么呢? 1.完成对处理器时钟的初始化 2. DDR的初始化 3.给gd_t *gd_ptr赋值 (用于存储uboot镜像的长度,重定位地址,重定位偏移量等信息)。在uboot搬运到DDR中运行前进行最小系统的初始化,之后就将uboot搬运到ddr中进行运行。(重要!此时Norfalsh和SRAM的任务就完成了(这俩就没用了),现在uboot就在ddr中运行了)。

Nand/SPI/eMMC:它们的I/O接口并没有随机存取外部地址总线,无法向SRAM随机访问地址上的数据,它必须以区块性的方式进行读取,而norflash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地<读取>其内部的每一个字节。所以通常的方式是一上电的过程中自动缓存Nand中的Uboot数据到SRAM中,然后在SRAM中执行,在其中完成DDR、NAND的初始化,这时可以将代码搬到SDRAM中运行。

  • 编译地址、运行地址

编译地址: 32位的处理器,它的每一条指令是4个字节,以4个字节存储顺序,进行顺序执行,CPU是顺序执行的,只要没发生什么跳转,它会顺序进行执行行, 编译器会对每一条指令分配一个编译地址,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。

运行地址:是指程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里,哪里就是运行的地址。

在relocation过程中,不是简单的将编译地址完整按顺序的搬到SDRAM中的运行地址空间的,而是会有一定的计算重新排布,参考uboot的relocation原理详细分析。

好,接下来我们来看看不带Trust 但支持SPL功能传统Uboot的启动模式,以S5PV210为例。

S5PV210 U-Boot Boot Flow

一图胜千言。参考三星官方Internal ROM Booting

是不是有一种感觉,SPL有点多余?

这个主要原因是对于一些SOC来说,它的内部SRAM可能会比较小,小到无法装载下一个完整的uboot镜像,那么就需要spl,它主要负责初始化外部RAM和环境,并加载真正的uboot镜像到外部RAM中来执行。所以由此来看,SPL应该是一个非常小的loader程序,可以运行于SOC的内部SRAM中,它的主要功能就是加载真正的uboot并运行之。

Rk3399 U-boot Boot Flow

还是一图胜千言。

从上图可以看出:

Boot Flow 1 is typical Rockchip boot flow with Rockchip miniloader(即是RK特殊定制的方式);

Boot Flow 2 is used for most SoCs with U-Boot TPL for ddr init and SPL for trust(ATF/OP-TEE) load and run into next stage(常规的做法);

Boot Flow 3 is use only for RK3399 with SPL ATF support(常规的做法);

Boot Flow 4 is used for armv7 SoCs which do not support trust(不带trust,和上面三星v210类似);

再加上ATF后的理解:

写在后面

其实还有很多主题可以完善,如 到底是如何relocation的,如何boot Linux image,uboot是如何编译的,driver的架构如何,ATF里面的psci如何操作休眠唤醒的。

待…………

参考:
深入理解uboot 2016 - 基础篇(处理器启动流程分析
tiny210(s5pv210)上电启动流程(BL0-BL2)

从头开始移植Ubuntu系统到ARM平台(基于全志H3)

Posted on 2018-04-20 | In Linux |
Words count in article 667 | Reading time 3

起点

在ARM SOC上移植Ubuntu系统并不是一件容易的事情,要对镜像文件的组成,系统的启动顺序非常熟悉。在网上searching一番,发现一个很神奇的网站->Armbian,这个网站开发者在大量的开发板上做了移植工作,包括allwinner H3/H5、Rockchip RK3328、amlogic S905x等,并且有详细的文档,以及开源的编译系统。

其实,参考Armbian的文档,即可搭建好完整编译环境,并针对你的板子(前提是里面已经支持的SoC)修改uboot、kernel、编译脚本即可定制,都是开源的。我尝试在H3平台上搭建,并成功制作了镜像,进到了系统。

编译系统

如果需要定制一些功能,如添加、删除一些脚本,应用程序,就必须完整看懂整个编译逻辑。Armbian上介绍是:

从khadas开发板网站 上有更形象的图(但未必准确,尤其是对Initramfs与inittrd的理解):

对照编译脚本来看:

从根目录compile.sh开始,进入main.sh,主要工作在后者里完成,关键步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1# Check and install dependencies, directory structure and settings
prepare_host
...........
2# 下载uboot及kernel
fetch_from_repo "$BOOTSOURCE" "$BOOTDIR" "$BOOTBRANCH" "yes"
fetch_from_repo "$KERNELSOURCE" "$KERNELDIR" "$KERNELBRANCH" "yes"
...........
3# Compile u-boot if packed .deb does not exist
compile_uboot
...........
4# Compile kernel if packed .deb does not exist
compile_kernel
...........
5# create board support package /create desktop package / build additional packages
create_board_package
create_desktop_package
chroot_build_packages
...........
6# Starting rootfs and image building process
debootstrap_ng
...........
7# make the image
prepare_partitions
create_image

后记

  1. 里面有非常多的细节,要花些时间看明白,尤其要对shell scripts比较熟悉。
  2. ramfs、Initramfs、ramdisk、inittrd、rootfs、tmpfs的区别,参考ramfs-rootfs-initramfs。看了半天貌似也没看懂。简单来说,ramdisk是一种基于ram的块设备,ramfs是一种基于ram的文件系统,开发ramfs的目的是因为ramdisk浪费了太多的内存cache页。initrd是init ramdisk的缩写,initramfs是init ramfs的缩写。名称里加了init前缀,代表它们具有了引导内核启动的功能。
  3. 自己做的过程中,添加了一个自动挂载硬盘的功能,通过udev,但发现FAT32可以挂载,NTFS不能,网上searching一番并多次尝试,最终找到可行解决方案。参考如下自动挂载。也有第三方的解决方案,如autofs, HAL, udisks, udisks2, usbmount,并未尝试。另,一些有用的调试命令:
1
2
3
udevadm info /dev/sda1 --此命令可以查看相关设备的udev属性,依据此来写rules。
udevadm monitor --udev --观察 uevent事件
blkid fdisk lsblk

以上只是一些索引记录,供参考。

一文了解Linux command and Shell

Posted on 2018-04-20 | In shell |
Words count in article 1,644 | Reading time 7

系统命令何其多

Linux下命令那么多,好几千个,怎么办?用man查询,如 man ls(查看ls的用法)。


看一看有哪些常用命令->

用户管理

  • UID,GID

常用命令:id, who, /etc/passwd, groups

账号管理: useradd, passwd, usermod, userdel, groupadd, groupdel, w

文件管理

常用命令: pwd, touch, chmod, chown, which, whereis

  • find

查找指定文件并删除:
find android/ -maxdepth 3 -type f -a -name 2.log -delete

删除所有文件仅保留特定文件:
find android/ -type f -not -a -name '*.java' -delete

查找指定文件并搜索:
find android/ -type f -a -name '*.java' | grep -rn "activity"

  • 打包: zip, tar

例子:Android压缩SDK:

1
tar -zcvf xxx.tar.gz sdk_directory_name --exclude=.repo --exclude=.git --exclude=sdk_directory_name/uboot/build --exclude=sdk_directory_name/out --exclude=sdk_directory_name/ S82_SDK_20141121.tar.gz

分卷压缩,网盘上传文件大小有限制这个命令会用到:

tar -zcvf - .repo/ |split -b 4000M - xxx_sdk.tar.gz

文件系统

df, fdisk, mount, lsblk, blkid, /etc/fstab(设置自动挂载),ln -s(软链接)

字符处理

grep:-r : 迭代到子文件夹; -n:行号; -i:不区分大小写

sort:-n 数字排序 -r 反向排序 -t 指定分隔符

uniq:删除重复内容

cut:截取文本

网络管理

指定IP地址:ipconfig eth0 192.168.1.6 netmask 255.255.255.0

手动打开断开网卡:ifconfig eth0 up/ifconfig etho down

查看系统路由表:route -n

DNS: /etc/hosts, /etc/resolv.conf

进程管理

ps, top, kill, nice

正则表达式

8种字符串截取方法

  • sed与awk的区别:

如果文件是格式化的,即由分隔符分为多个域的,优先使用awk;

awk适合按列(域)操作,sed适合按行操作;

awk适合对文件的抽取整理,sed适合对文件的编辑。


shell编程

shell 内建命令

由bash自身提供的命令,而不是/bin下某个可执行文件,比如:cd,source。如何确定,通过type。

alias,别名,可以在.bashrc中定制。

任务前后台切换:bg、fg、jobs。可与Ctrl+z、&联合使用。典型场景是运行比较耗时任务。

./, ., source 三者执行shell的区别。

exec:不启动新的shell,而是用要被执行的命令替换当前的shell脚本。exec命令后其他命令将不再执行,且会断开ssh链接。所以一般放到一个子脚本中运行。

source,就是让script在当前shell内执行、 而不是产生一个sub-shell来执行。 由于所有执行结果均在当前shell内执行、而不是产生一个sub-shell来执行。跟 . xxx.sh 一样效果。

./xxx.sh 是直接fork一个子进程来执行。

export:跨脚本传递变量。

read:从标准输入读取一行。

脚本参数: $1 第一个参数 $2 第二个参数 …… $@ 所有参数 $# 参数个数 $0 脚本本身 $? 上一条命令返回值

基础

  1. 局部变量,只在某个shell中生效。也可用local声明,在函数中生效。
  2. 环境变量,也叫全局变量。系统中有预设一些环境变量,如HOME,PATH,可以通过 echo $PATH访问。
    如果需要在shell中导出变量给其他子shell中使用可以通过:export VAR=value。
  3. 变量的赋值与取值。变量名与值用=紧紧相连,中间不能有空格。${}是比$更保险的做法。如果值也是引用变量,要用"",如name="${name1}"。unset可以取消变量。只读变量通过readonly声明,或是declare -r。
  4. 转义通过\来让特殊字符输出。
  5. 命令替换是指将标准输出作为值赋给某个变量:$(命令).
  6. ()与{}的差别:

    () 将command group置于sub-shell(子shell)中去执行,也称 nested sub-shell。
    {} 则是在同一个shell内完成,也称non-named command group。

  7. 常见算术运算符大多需要结合shell的内建命令let来使用。

  8. $(()) 用来作整数运算的.
  9. Wildcard与Regular Expression的差别.

    wildcard只作用于argument的path上;而RE却只用于”字符串处理” 的程序中,如某些文字处理工具之间:grep, perl, vi,awk,sed,等等, 常用于表示一段连续的字符串,查找和替换,这与路径名一点关系也没有。

测试判断与循环

测试结构:

test expression or [ expression ] 括号内两边有空格 建议采用后面的方式,更容易跟if while case 这些连用。

文件测试,常用参数:-e 文件或目录是否存在;-f 文件是否存在;-d 目录是否存在。

字符串:-z 是否为空;-n 非空返回真;= !=

整数比较:-eq -gt -lt

逻辑:两种方式 ! -a -o or ! && ||。

command1 && command2 # command2只有在command1的RV为0(true)的条件下执行。

command1 || command2 # command2 只有在command1的RV为非0(false)的条件下执行。

[]与[[]]的区别Reference:

当使用‘-n’‘-z’这种判断方式时,‘[]’需要在其中的变量外侧加上双引号,与test命令的用法一致,而使用[[]]时不用。

判断某个变量的值是否满足某个正则表达式,可以用符号=~ + [[]]。

if 判断结构:

1
2
3
4
5
6
7
if [ expression ]; then
cmd1
elif [ exp1 ]; then
cmd2
else
cmd3
fi

case 判断结构:

1
2
3
4
5
6
7
case VAR in

var1) cmd1;;
var2) cmd2;;
*) cmd3;;

esac

for 循环:

1
2
3
4
5
6
for VAR in (list)
do

cmd

done

while 循环:

1
2
3
4
while expression
do
cmd
done

until循环结构:(测试假值)。

select循环,是一种菜单扩展循环方式,等待用户输入在执行。

1
2
3
4
select MENU in (list)
do
cmd
done

循环控制:break、continue。

重定向

系统在启动一个进程时会同时打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr),分别用文件标识符0、1 、2来标识。标准输入为键盘,标准输出及错误输出默认为显示器或是串口。

> :标准输出覆盖重定向,会覆盖原始文件。

1
2
3
4

ls -l /usr/ > ls_usr.txt
等价于
ls -l /usr/ 1> ls_usr.txt

>>: 追加重定向。不清空原始文件。

>\&: 标识输出重定向,将一个标识的输出重定向到另一个标识的输入。

1
2
3
4
COMMAND > stdout_stderr.txt 2>&1 
#2>&1代表 错误输出重定向到标准输出,同时打印到文件中。

2> /dev/null #丢弃错误输出

< : 标准输入重定向。

<<: 这是所谓的here document, 它可以让我们输入一段文本, 直到读到<< 后指定的字符串。比方说:

1
2
3
4
5
$ cat <<EOF
first line here
second line here
third line here
EOF

| : 管道,将一个命令的输出作为另一个命令的输入。

最后

  • 还不错的参考

shell 十三问

Build github pages with Hexo+NexT

Posted on 2018-04-17 | In web |
Words count in article 796 | Reading time 3

主要记录在Windows下搭建博客系统的一些简要步骤,具体参考相关链接。

PS:为了建这个博客,Search了网络上的一些信息,很多都是只言片语且不够全面,甚至过时。所以最好的做法是参考原始出处、原始文档,一是原始的更全面,二是会时时更新。

系统环境配置(Windows)

主要需要以下三个软件,按顺序安装(点击进官网查看相关):

  • git-scm
  • Nodejs
  • Hexo

Hexo相关

  • 安装Hexo
1
2
3
4
5
6
7
$ cd d:/hexo
$ npm install hexo-cli -g
$ hexo init blog
$ cd blog
$ npm install
$ hexo g # 或者hexo generate
$ hexo s # 或者hexo server,可以在http://localhost:4000/ 查看
  • Hexo常用命令及用法-点击进官网

    • hexo generate (hexo g) 生成静态文件,会在当前目录下生成一个新的叫做public的文件夹
    • hexo server (hexo s) 启动本地web服务,用于博客的预览
    • hexo deploy (hexo d) 部署播客到远端(比如github, heroku等平台)
    • hexo new "postName" #新建文章
    • hexo new page "pageName" #新建页面
    • hexo clean 清除静态资源

Next主题

  • clone NexT主题

    git clone https://github.com/iissnan/hexo-theme-next

其中有个问题,如何将站点及主题的配置整合到一起,并合理的保存,参考NexT的 README中描述。

  • 相关配置(大多在主题_config.yaml中可配置)
    • 添加categories、tags(参考官方github README)
    • 站点访问次数
      • 不蒜子 http://busuanzi.ibruce.info/
      • busuanzi_count: true
    • 站内搜索
      • algolia_search
      • local_search
    • 文章访问次数
      • leancloud_visitors
    • 评论添加(有多种配置,参考_config.yaml)
      • youyan_uid (http://www.uyan.cc)

项目托管到github

  • 创建github pages
    • 在github上新建仓库,且仓库的名字必须是username/username.github.io
  • 部署
    • 配置ssh-key:ssh-keygen -t rsa -C "注册git的邮箱";
    • 打开https://github.com/settings/ssh ->new SSH key 添加密钥,title随便写,key为id_rsa.pub中所有内容;
    • 验证是否能否连接到github: ssh -T git@github.com
    • 同步到github
      • 首先安装:npm install hexo-deployer-git --save
      • 在blog目录执行: hexo d

按以上配置,基本上一个github pages基本完成,接下来要考虑如何编辑。

编辑相关

  • Markdown 编辑器选择

    • Visual studio Code
    • HexoEditor
  • 如何插入图片(Link)

    • post_asset_folder: true
    • asset_img
  • 如何插入代码(Link)

    • 采用 codeblock
    • 3个`

```[language] [title] [url] [link-text]

代码

```

[language] 是代码语言的名称,用来设置代码块颜色高亮,非必须;

[title] 是顶部左边的说明,非必须;

[url] 是顶部右边的超链接地址,非必须;

[link text] 如它的字面意思,超链接的名称,非必须。

亲测这 4 项应该是根据空格来分隔,而不是[],故请不要加[]。除非如果你想写后面两个,但不想写前面两个,那么就必须加[]了,要这样写:[] [] [url] [link text]。

  • 如何添加categories&tags(Link)

总结

为什么选择github pages+hexo方案,最大的原因是免费,且有清新简约的Hexo引擎加上各种主题,然后可以在github上面保存Tracking。

在实施的过程中,最好是通读原始的文档,然后多实际尝试,一步一步会达到想要的结果。

OuyangSheng

OuyangSheng

9 posts
5 categories
5 tags
GitHub
  • 获取 Elon Musk 的新闻
  • 这个建站教程diao
© 2018 OuyangSheng
Powered by Hexo
Theme - NexT.Pisces
0%