openwrt (三)入门FAQ

版权声明: https://blog.csdn.net/tjd0227/article/details/80527903

openwrt作为一个基于linux开发的比较完善的嵌入式系统,可以快速移植到各种平台上。初次下载开源代码后,简单浏览后很是诧异,居然没看到uboot和kernel部分的代码,甚至没看到任何模块的代码,最多只是些patch和配置文件。
按照文档编译后,发现多了些工程目录,进而发现了很多源码,猜测到大概是Makefile或feed脚本在编译时在线下载的代码。为了后来者,初次入门openwrt少踩坑,完成此章,内容多为我入门的困惑与解答。



1. uboot和kernel在哪里?

事实上,拿到marvell release代码后会看到:

    $ tree -L 1 marvell/
    marvell/
    ├── fastpath
    ├── fota
    ├── linux
    ├── lte-telephony
    ├── obm
    ├── services
    ├── swd
    ├── uboot
    └── webui

这些明显是marvell自己定制过的一些代码包,针对这个平台算是找到了正确的代码位置,但我们并不了解这个过程,并且开源代码部分是没有这种结构的,所以我们要继续探究下去,提出问题:

  1. 官方release的openwrt没有定制的情况下uboot和kernel在哪?
  2. marvell这种定制是如何实现的?

在目录结构和功能不了解的情况下会感觉无从下手而四处乱摸,通过上一章了解了目录结构,很自然的就会想到package目录,进去看看:

    $ cd package
    $ tree -L 1 boot 
    boot/
    ├── uboot-mvebu
    ├── uboot-mxs
    ├── uboot-omap 
    ├── uboot-oxnas
    ├── ...
    └── yamonenv

最终发现,package/boot/uboot-mmp/ :

    $ cat package/boot/uboot-mmp/Makefile
    include $(TOPDIR)/rules.mk

    PKG_NAME:=u-boot
    PKG_VERSION:=2014
    PKG_RELEASE:=1

    USE_SOURCE_DIR:=$(MRVLDIR)/uboot
    PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
    PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_NAME)-$(PKG_VERSION)

    include $(INCLUDE_DIR)/package.mk

这里面看到了’MRVLDIR’,查询后发现MRVLDIR:=$(TOPDIR)/marvell,

    $ ls -l build_dir/target-arm_cortex-a7+neon-vfpv4_uClibc-1.0.25_eabi/u-boot-nezha3_dkb/
    total 0
    lrwxrwxrwx 1 tjd tjd 56 May 15 15:56 u-boot-2014 -> /home/tjd/datadisk/marvell/OpenWrt/openwrt/marvell/uboot

确认无疑了!!

package/boot/uboot-mmp/就是marvell的uboot定义的位置,变量USE_SOURCE_DIR指定了uboot使用的源码。

知道了marvell定制uboot源码的方法,再看一下官方release版本的uboot,已Beaglebone black板项目为例:

    $ cat package/boot/uboot-omap/Makefile
    include $(TOPDIR)/rules.mk
    include $(INCLUDE_DIR)/kernel.mk

    PKG_VERSION:=2017.01
    PKG_RELEASE:=2

    PKG_HASH:=6c425175f93a4bcf2ec9faf5658ef279633dbd7856a293d95bd1ff516528ecf2

    include $(INCLUDE_DIR)/u-boot.mk
    include $(INCLUDE_DIR)/package.mk

    define U-Boot/Default
      BUILD_TARGET:=omap
      UBOOT_IMAGE:=u-boot.img MLO
      UENV:=default
    endef

    define U-Boot/omap4_panda
      NAME:=Pandaboard
      BUILD_DEVICES:=omap4-panda
    endef

    define U-Boot/am335x_boneblack
      NAME:=TI AM335x BeagleBone Black
      BUILD_DEVICES:=am335x-boneblack
    endef
    ...

很遗憾,这里没有USE_SOURCE_DIR指定源码位置,只是指定了使用的uboot的版本号,目录中也没有源码:

    package/boot/uboot-omap/
    ├── files
    │   └── uEnv-default.txt
    ├── Makefile
    └── patches
        ├── 101-disable-thumb-omap3.patch
        ├── 102-minify-spl.patch
        ├── 103-disable-fat-write-spl.patch
        ├── 104-omap3-overo-enable-thumb.patch
        └── 105-serial-ns16550-bugfix-ns16550-fifo-not-enabled.patch

    2 directories, 7 files

这里保存了针对bbb的uboot相关patch,联想到与硬件相关的内容是以patch存在的,u-boot应该是官方发布的原版代码,上一章提到了dl目录保存了从网络上下载的代码包,编译时会解压源码包到build_dir中,现在看一下:

    $ ls build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/
    api    cmd        configs  drivers   fs       Kconfig   MAINTAINERS  MLO.byteswap  README           spl         tools       u-boot.cfg          u-boot.lds        u-boot.srec
    arch   common     disk     dts       include  lib       Makefile     net           scripts          System.map  u-boot      u-boot.cfg.configs  u-boot.map        u-boot.sym
    board  config.mk  doc      examples  Kbuild   Licenses  MLO          post          snapshot.commit  test        u-boot.bin  u-boot.img          u-boot-nodtb.bin

前面看到package/boot/uboot-omap/中有patch,是不是已经合入了呢? 对比看看:

    $ cat package/boot/uboot-omap/patches/101-disable-thumb-omap3.patch
    Index: u-boot-2017.01/include/configs/ti_omap3_common.h
    ===================================================================
    --- u-boot-2017.01.orig/include/configs/ti_omap3_common.h
    +++ u-boot-2017.01/include/configs/ti_omap3_common.h
    @@ -80,4 +80,9 @@
     /* Now bring in the rest of the common code. */
     #include <configs/ti_armv7_omap.h>

    +/* beagleboard doesnt boot with thumb */
    +#ifdef CONFIG_SYS_THUMB_BUILD
    +#undef CONFIG_SYS_THUMB_BUILD
    +#endif
    +
     #endif /* __CONFIG_TI_OMAP3_COMMON_H__ */

    $ diff dl/u-boot-2017.01_unpack/include/configs/ti_omap3_common.h build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/include/configs/ti_omap3_common.h
    82a83,87
    > /* beagleboard doesnt boot with thumb */
    > #ifdef CONFIG_SYS_THUMB_BUILD
    > #undef CONFIG_SYS_THUMB_BUILD
    > #endif

看来patch是已经打上去了。

  1. 官方的uboot先解压到build_dir中,再打上package/boot/uboot-xxx/patches中的patch,再编译。
  2. marvell通过变量USE_SOURCE_DIR指定本地代码库,连接到build_dir中,然后编译。

再看看kernel应该是类似的:区别是kernel不在package中而是target中,引用外部库的变量不是USE_SOURCE_DIR而是CONFIG_EXTERNAL_KERNEL_TREE

    $ cat target/linux/mmp/Makefile
    include $(TOPDIR)/rules.mk

    ARCH:=arm
    BOARD:=mmp
    BOARDNAME:=Marvell MMP
    SUBTARGETS=pxa1826
    FEATURES:=squashfs jffs2_nand nand ubifs

    LINUX_VERSION:=3.10.33
    CONFIG_EXTERNAL_KERNEL_TREE:=$(MRVLDIR)/linux

    define Target/Description
            Build firmware images for Marvell MMP SoC
    endef

    include $(INCLUDE_DIR)/target.mk

2. 如何编译自己的代码包?

用户态模块

新建getevent包最终如下:

$ tree package/xxx/
xxx/
└── getevent
    ├── Makefile
    └── src
        ├── getevent.c
        ├── getevent.h
        └── Makefile

配置顶层Makefile

    $ vi package/xxx/getevent/Makefile
    include $(TOPDIR)/rules.mk

    PKG_NAME:=getevent
    PKG_RELEASE:=1

    # 定义编译软件包的路径
    PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

    include $(INCLUDE_DIR)/package.mk

    # 软件包描述,可以在make menuconfig中体现出来
    define Package/getevent
      SECTION:=Xxxx
      CATEGORY:=xxx ddd
      TITLE:=linux inputevent test tool
    endef

    # 本包安裝的配置文件,一行一个
    define Package/dnsmasq/conffiles
    #/etc/config/dhcp
    #/etc/dnsmasq.conf
    endef

    # make package/xxx/getevent/prepare时被执行
    define Build/Prepare
            mkdir -p $(PKG_BUILD_DIR)
            $(CP) ./src/* $(PKG_BUILD_DIR)/
    endef

    # 软件包的介绍信息
    define Package/helloworld/description
        helloworld,first self-made.
    endef

    # 对执行./configure时的特殊处理
    define Build/Configure
    endef

    # 执行make或make package/xxx/getevent/compile时处理 
    define Build/Compile
            $(MAKE) -C $(PKG_BUILD_DIR) \
                    CC="$(TARGET_CC)" \
                    CFLAGS="$(TARGET_CFLAGS) -Wall" \
                    LDFLAGS="$(TARGET_LDFLAGS)"
    endef

    # make install时处理 
    # INSTALL_DIR 决定了通过opkg安装ipk的目标目录
    define Package/getevent/install
            $(INSTALL_DIR) $(1)/usr/sbin
            $(INSTALL_BIN) $(PKG_BUILD_DIR)/getevent $(1)/usr/sbin/
    endef

    $(eval $(call BuildPackage,getevent))

编辑src目录下的Makefile

    CC = gcc
    CFLAGS = -Wall
    OBJS = getevent.o

    all: getevent

    %.o: %.c
            $(CC) $(CFLAGS) -c -o $@ $<

    getevent: $(OBJS)
            $(CC) -o $@ $(OBJS)

    clean:
            rm -f getevent *.o

注意,这里一个代码包中有两个Makefile

下一步时make menuconfig,选择CONFIG_PACKAGE_getevent=mCONFIG_PACKAGE_getevent=y否则编译不出东西,会报:

    WARNING: skipping getevent -- package not selected

config配置成m时会编译成ipk文件,可以按需安装;配置成y时,是buildin,内置到release软件中,无需安装。

内核模块

最终包如下:

    $ tree  khello/
    khello/
    ├── Makefile
    └── src
        ├── khello.c
        └── Makefile

    1 directory, 3 files

配置顶层Makefile

    $ cat khello/Makefile
    #
    # Copyright (C) 2008 OpenWrt.org
    #
    # This is free software, licensed under the GNU General Public License v2.
    # See /LICENSE for more information.
    #

    include $(TOPDIR)/rules.mk
    include $(INCLUDE_DIR)/kernel.mk

    PKG_NAME:=khello
    PKG_RELEASE:=2

    include $(INCLUDE_DIR)/package.mk

    define KernelPackage/khello
      # 指定menuconfig的路径
      SUBMENU:=Other modules
      TITLE:=kernel Helloworld test
      # 依赖的模块
      DEPENDS:=
      # 目标文件
      FILES:=$(PKG_BUILD_DIR)/khello.ko
      KCONFIG:=
    endef

    define KernelPackage/khello/description
     Kernel module for helloword example.
    endef

    EXTRA_KCONFIG:= \
            CONFIG_HELLO=m

    EXTRA_CFLAGS:= \
            $(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
            $(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \

    MAKE_OPTS:= \
            ARCH="$(LINUX_KARCH)" \
            CROSS_COMPILE="$(TARGET_CROSS)" \
            SUBDIRS="$(PKG_BUILD_DIR)" \
            EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
            $(EXTRA_KCONFIG)

    define Build/Prepare
            mkdir -p $(PKG_BUILD_DIR)
            $(CP) ./src/* $(PKG_BUILD_DIR)/
    endef

    define Build/Compile
            $(MAKE) -C "$(LINUX_DIR)" \
                    $(MAKE_OPTS) \
                    modules
    endef

    $(eval $(call KernelPackage,khello))

编辑src目录下的Makefile

    $ cat package/revoview/khello/src/Makefile
    obj-$(CONFIG_HELLO)  += khello.o

编辑khello.c

    $ cat package/revoview/khello/src/khello.c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>

    static int __init hello_init(void)
    {
            printk("Hello Kernel\n");
            return 0;
    }
    module_init(hello_init);

    static void __exit hell_exit(void)
    {
            printk("Bye Kernel");
    }
    module_exit(hell_exit);
    MODULE_LICENSE("GPL");

3. 模块如何单独编译?

  1. 首先有个顶层Makefile,如上面的两个案例

  2. 如何模块需要区别于默认编译选项,Makefile中可以包含下面几个重要元素:

    • Build/Prepare

      make xxx/xxx/prepare

    在编译前解压或在build_dir/准备代码时调用,如:

    define Build/Prepare
        mkdir -p $(PKG_BUILD_DIR)
        $(CP) ./src/* $(PKG_BUILD_DIR)/
    endef
    
    • Build/Configure

      make xxx/xxx/configure

    在编译前执行./configure 做特殊处理,如:

    define Build/Configure
        $(CP) $(SCRIPT_DIR)/config.guess $(SCRIPT_DIR)/config.sub     
        $(PKG_BUILD_DIR)/support/
        $(call Build/Configure/Default, \
              --enable-shared \
                --enable-static \
                --without-curses \
        )
    endef
    

    也可以这样用:

    define Build/Configure
            $(call Build/Configure/Default)
            $(if $(CONFIG_PCAP_HAS_USB),,$(SED) '/^#define PCAP_SUPPORT_USB/D' $(PKG_BUILD_DIR)/config.h)
            $(if $(CONFIG_PCAP_HAS_USB),,$(SED) 's/pcap-usb-linux.c *//' $(PKG_BUILD_DIR)/Makefile)
            $(if $(CONFIG_PCAP_HAS_BT),,$(SED) '/^#define PCAP_SUPPORT_BT/D' $(PKG_BUILD_DIR)/config.h)
            $(if $(CONFIG_PCAP_HAS_BT),,$(SED) 's/pcap-bt-linux.c *//' $(PKG_BUILD_DIR)/Makefile)
    endef
    

    需要注意的是:$(call Build/Configure/Default)是必需的。

    • Build/Compile

      make xxx/xxx/compile

    make时执行,可以增加编译参数

    define Build/Compile
            $(TARGET_CONFIGURE_OPTS) \
                    CFLAGS="$(TARGET_CFLAGS) -I$(STAGING_DIR)/usr/include -I$(PKG_BUILD_DIR)" \
                    LDFLAGS="$(TARGET_LDFLAGS)" \
                    $(MAKE) -C $(PKG_BUILD_DIR)
    endef
    
    • Build/InstallBuild/InstallDev

      make xxx/xxx/install

    在编译完成,安装时执行的特殊处理,如:

    define Build/InstallDev
            $(INSTALL_DIR) $(1)/usr/include
            $(CP) $(PKG_INSTALL_DIR)/usr/include/lua{,lib,conf}.h $(1)/usr/include/
            $(CP) $(PKG_INSTALL_DIR)/usr/include/lauxlib.h $(1)/usr/include/
            $(CP) $(PKG_INSTALL_DIR)/usr/include/lnum_config.h $(1)/usr/include/
            $(INSTALL_DIR) $(1)/usr/lib
            $(CP) $(PKG_INSTALL_DIR)/usr/lib/liblua.{a,so*} $(1)/usr/lib/
            ln -sf liblua.so.$(PKG_VERSION) $(1)/usr/lib/liblualib.so
            $(INSTALL_DIR) $(1)/usr/lib/pkgconfig
            $(CP) $(PKG_BUILD_DIR)/etc/lua.pc $(1)/usr/lib/pkgconfig/
    endef
    

4. ipk是如何编译出来的

openwrt的编译要区分是不是buildin, 对于buildin是指,选定的包编译时就就安装到rootfs中,否则,就是编译成ipk文件,通过opkg命令安装。

区分是否buildin通过menuconfig配置,最终体现在.config中。

        < CONFIG_PACKAGE_getevent=y
        ---
        > CONFIG_PACKAGE_getevent=m

`=m` 代表编译成ipk, 目标:`bin/pxa1826/packages/base/getevent_1_pxa1826.ipk`

`=y` 代表buildin,目标:`staging_dir/target-arm_cortex-a7+neon-vfpv4_uClibc-1.0.25_eabi/root-mmp/usr/sbin/getevent`

5. Patches是如何自动打上去的?

通过make -n提取的make V=s package/utils/busybox/{clean,prepare}的过程如下

    . /source/marvell/include/shell.sh; bzcat /source/marvell/dl/u-boot-2017.01.tar.bz2 | tar -C /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/.. -xf -
    [ ! -d ./src/ ] || cp -fpR ./src/. /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01
    if [ -d "./patches" ] && [ "$(ls ./patches | wc -l)" -gt 0 ]; then export PATCH="patch"; if [ -s "./patches/series" ]; then sed -e s,\\\#.*,, ./patches/series | grep -E \[a-zA-Z0-9\] | xargs -n1 /source/marvell/scripts/patch-kernel.sh "/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01" "./patches"; else /source/marvell/scripts/patch-kernel.sh "/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01" "./patches"; fi; fi
    1. 解压
bzcat /source/marvell/dl/u-boot-2017.01.tar.bz2 | tar -C /source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01/.. -xf -
    1. 打patch

    “`
    /source/marvell/scripts/patch-kernel.sh “/source/marvell/build_dir/target-arm_cortex-a8+vfpv3_musl_eabi/u-boot-am335x_boneblack/u-boot-2017.01“ “./patches”


可以在package/boot/uboot-omap目录下运行patch-kernel.sh测试。

# 6. 如何创建自己的patch?

* 安装quilt

        sudo apt-get install quilt

* 准备quilt的配置文件

        cat > ~/.quiltrc <<EOF
        QUILT_DIFF_ARGS="--no-timestamps --no-index -p ab --color=auto"
        QUILT_REFRESH_ARGS="--no-timestamps --no-index -p ab"
        QUILT_SERIES_ARGS="--color=auto"
        QUILT_PATCH_OPTS="--unified"
        QUILT_DIFF_OPTS="-p"
        EDITOR="nano"
        EOF

* 创建第一个patch


        $ make  package/utils/fbtest/{clean,prepare} V=s QUILT=1
        $ cd build_dir/target-*/fbtest
        $ quilt new patches/0001-test.patch
        $ quilt edit fbtest.c
        查看修改的内容
        $ quilt diff
        刷新0001-test.patch
        $ quilt refresh
        把patch挪到package目录
        $ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/

        编译看看patch是否生效

        $ make  package/utils/fbtest/{clean,prepare} V=s

* 增加 patch

和上面步骤一样,只要注意patch的名字序号递增。

        $ make  package/utils/fbtest/{clean,prepare} V=s QUILT=1
        $ cd build_dir/target-*/fbtest
        $ quilt new patches/0002-test2.patch
        $ quilt edit Makefile
        查看修改的内容
        $ quilt diff
        刷新0002-test.patch
        $ quilt refresh
        把patch挪到package目录
        $ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/

        编译看看patch是否生效

        $ make  package/utils/fbtest/{clean,prepare} V=s

* 修改patch

        $ make  package/utils/fbtest/{clean,prepare} V=s QUILT=1
        $ cd build_dir/target-*/fbtest
        $ quilt push patches/0002-test2.patch
        $ quilt edit Makefile
        查看修改的内容
        $ quilt diff
        刷新0002-test.patch
        $ quilt refresh
        把patch挪到package目录
        $ cp build_dir/target-*/fbtest/patches/*.patch package/utils/fbtest/patches/

        编译看看patch是否生效

        $ make  package/utils/fbtest/{clean,prepare} V=s


生成patch特别要注意的是:

> * 各个patch是不能有互相依赖关系的
> * 如果有依赖关系需要做特殊处理,比如`0002`依赖`0001`,需要将`0001`先导入(但不能用`quilt` 导入,可以是`patch -p1`之类的),然后再用quilt生成`0002` 
> * 最后一步`cp patch`时不要将`series`文件也拷贝到package包中。

# 7. rootfs源头在哪?

有两个位置:
> 1. package/base-file
> 2. target/linux/xxx/base-files

# 8. 过滤uevent的json文件如何理解?

etc/hotplug.json为例:

[ "case", "ACTION", {
    "add": [#uevent的ACTION事件类型为add
        [ "if",
            [ "and", #"if...and"表示由多个条件同时满足if才为真,这里是要求uevent中既有MAJOR也要有MINOR变量
                [ "has", "MAJOR" ],
                [ "has", "MINOR" ],
            ],
            [#同时满足后走这里
                [ "if",
                    [ "or",#"if..or"表示满足下面条件的其中一个if则为真,这里是eq和regex满足一个则为真
                        [ "eq", "DEVNAME",#eq可以匹配多个值,只要uevent中DEVNAME为下面4各任意一个则为真
                            [ "null", "full", "ptmx", "zero" ],
                        ],#regex,正则匹配,满足下面一个表达式则为真
                        [ "regex", "DEVNAME",
                            [ "^gpio", "^hvc", "^tty" ],#DEVNAME以gpio、hvc、tty开头
                        ],
                    ],
                    [#if条件满足后,运行下面hotplug指令
                        [ "makedev", "/dev/%DEVNAME%", "0666" ],
                        [ "return" ],
                    ]
                ],
                ....
                [ "if",
                    [ "has", "DEVNAME" ],"if..has",如果uevent中存在DEVNAME,则为真
                    [ "makedev", "/dev/%DEVNAME%", "0644" ],
                ],
            ],
        ],
        [ "if",
            [ "has", "FIRMWARE" ],"if..has",如果uevent中存在FIRMWARE,则为真
            [
                [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ],
                [ "load-firmware", "/lib/firmware" ],
                [ "return" ]
            ]
        ],
    ],
    "remove" : [#uevent的ACTION事件类型为remove
        [ "if",
            [ "and",
                [ "has", "DEVNAME" ],
                [ "has", "MAJOR" ],
                [ "has", "MINOR" ],
            ],
            [ "rm", "/dev/%DEVNAME%" ]
        ]
    ]
} ],
[ "if",
    [ "eq", "SUBSYSTEM", "platform" ],
    [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
[ "if",
    [ "and",
        [ "has", "BUTTON" ],
        [ "eq", "SUBSYSTEM", "button" ],
    ],
    [ "exec", "/etc/rc.button/%BUTTON%" ]
],
[ "if",
    [ "eq", "SUBSYSTEM",
        [ "net", "input", "usb", "ieee1394", "block", "atm", "zaptel", "tty", "thermal", "power_supply" ]
    ],
    [ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
],
[ "if",
    [ "and",
        [ "eq", "SUBSYSTEM",
            [ "tty", "usb-serial" ]
        ],
        [ "regex", "DEVNAME", "^ttyUSB" ],
    ],
    [ "exec", "/sbin/hotplug-call", "tty" ]
],
....

总结一下:常用的判断语法就下面几种:

  • case
  • if…and
  • if…or
  • eq
  • regex

hotplug指令有下面几种:

  • makedev
    调用mknod创建设备节点
  • rm
    调用unlink函数删除文件
  • exec
    创建一个子进程取运行exec后面参数上的程序
  • button
    button跟exec功能一样,区别是button除了要建立子进程运行程序,还要在父进程中设置button的tiimeout事件,防止按键卡死问题。
  • load-firmware”
    通过内核模块升级硬件固件,一般是外设芯片的固件,需要驱动支持。

猜你喜欢

转载自blog.csdn.net/tjd0227/article/details/80527903
今日推荐