学习总结

[转载]make编译过程-Android10.0编译系统(三)

0 条评论 学习总结 无标签 adtxl

版权声明:本文为CSDN博主「IngresGe」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yiranfeng/article/details/109084082

1. 概述

上一节,我们理清了编译环境初始化的过程,环境变量已经加载,并配置了编译目标,接下来执行一个make命令我们就能够进行编译。make之后是怎么完成编译的,这个很有意思,我们一起往下探讨。

2. Android系统的编译历程

Android7.0 Google引入了soong构建系统,用来逐步替代GNU make的编译,因此在Android10.0 上,make执行后,我们走的是soong构建环境。
Android系统的编译历程:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-4.jpg

3.Soong编译系统家族成员

从下图可知,mk文件被编译成了 out/build-aosp_arm.ninja和out/build-aosp_arm-package.ninja,bp文件被编译成了out/soong/build.ninja,这三个ninja文件又被合并成out/combined-aosp_arm.ninja,最终通过ninja工具来编译out/combined-aosp_arm.ninja完成最终的编译。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-1.jpg

4. make的流程图

soong构建的流程图如下图所示:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-5.jpg

5. make()

执行完make命令后,会调用envsetup.sh的make()函数进行处理。

function make()
{
    _wrap_build $(get_make_command "$@") "$@"
}

从get_make_command()可以看出,make后,真正然后执行编译的入口是:build/soong/soong_ui.bash

function get_make_command()
{
    # If we're in the top of an Android tree, use soong_ui.bash instead of make
    if [ -f build/soong/soong_ui.bash ]; then
        # Always use the real make if -C is passed in
        for arg in "$@"; do
            if [[ $arg == -C* ]]; then
                echo command make
                return
            fi
        done
        echo build/soong/soong_ui.bash --make-mode
    else
        echo command make
    fi
}

6. soong_ui.bash

6.1 soong_ui.bash调用栈

image0538c1823bec74bf.png

soong_ui.bash执行过程:

  • source  microfactory.bash,得到一些函数命令, 例如:soong_build_go
  • 编译/build/soong/cmd/soong_ui/main.go,生成 out/soong_ui这个可执行程序
  • 执行命令:out/soong_ui --make-mode ,执行了make命令,会把"build/make/core/main.mk" 加到构建环境中,同时启动kati、blueprint-soong、ninja的编译。
    接下来根据调用栈的流程,来详细分析编译的过程。

6.2 [build/soong/soong_ui.bash]

soong_ui.bash 用来配置一些资源环境,得到一些函数命令,例如:soong_build_go,最终回退到根目录,执行out/soong_ui --make-mode进行真正的构建。

# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui

cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"

6.2.1 [/soong/../microfactory.bash]

得到build_go的函数命令,并提供 soong_build_go的函数执行方法

[/build/soong/scripts/microfactory.bash]
function soong_build_go
{
    BUILDDIR=$(getoutdir) \
      SRCDIR=${TOP} \
      BLUEPRINTDIR=${TOP}/build/blueprint \
      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
      build_go $@
}

source ${TOP}/build/blueprint/microfactory/microfactory.bash

6.2.2 [/blueprint/../microfactory.bash]

build_go主要目的就是用来构建生成 out/soong_ui这个可执行程序,用于参与最终的编译

[/build/blueprint/microfactory/microfactory.bash ]
function build_go
{
    # Increment when microfactory changes enough that it cannot rebuild itself.
    # For example, if we use a new command line argument that doesn't work on older versions.
    local mf_version=3

    local mf_src="${BLUEPRINTDIR}/microfactory"
    local mf_bin="${BUILDDIR}/microfactory_$(uname)"
    local mf_version_file="${BUILDDIR}/.microfactory_$(uname)_version"
    local built_bin="${BUILDDIR}/$1"
    local from_src=1

    if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then
        if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then
            from_src=0
        fi
    fi

    local mf_cmd
    if [ $from_src -eq 1 ]; then
        # `go run` requires a single main package, so create one
        local gen_src_dir="${BUILDDIR}/.microfactory_$(uname)_intermediates/src"
        mkdir -p "${gen_src_dir}"
        sed "s/^package microfactory/package main/" "${mf_src}/microfactory.go" >"${gen_src_dir}/microfactory.go"

        mf_cmd="${GOROOT}/bin/go run ${gen_src_dir}/microfactory.go"
    else
        mf_cmd="${mf_bin}"
    fi

    # GOROOT must be absolute because `go run` changes the local directory
    GOROOT=$(cd $GOROOT; pwd) ${mf_cmd} -b "${mf_bin}" \
            -pkg-path "github.com/google/blueprint=${BLUEPRINTDIR}" \
            -trimpath "${SRCDIR}" \
            ${EXTRA_ARGS} \
            -o "${built_bin}" $2

    if [ $? -eq 0 ] && [ $from_src -eq 1 ]; then
        echo "${mf_version}" >"${mf_version_file}"
    fi
}

soong_ui最终的编译命令展开为:

$(cd /prebuilts/go/linux-x86/; pwd) /out/microfactory_Linux 
  -b "/out/microfactory_Linux" \
  -pkg-path "github.com/google/blueprint=/build/blueprint" \
  -trimpath "./" \
  -pkg-path android/soong=/build/soong 
  -pkg-path github.com/golang/protobuf=/external/golang-protobuf} \
  -o "out/soong_ui" android/soong/cmd/soong_ui

从上面的流程可知,生成soong_ui经历几件事情:

  • 通过/build/blueprint/microfactory/microfactory.go 编译出/out/microfactory_Linux

  • 使用/out/microfactory_Linux来编译soong_ui
    microfactory是一个增量编译go程序的工具。它类似于“go install”,但不需要GOPATH。包->路径映射可以指定为命令行选项:

    -pkg-path android/soong=build/soong
    -pkg-path github.com/google/blueprint=build/blueprint

    其实microfactory就是一个高级一点的go命令,它自己由go编出来,又代替了一部分go的部分功能,鸡生蛋,蛋生鸡的故事,这里得到了完美解释 ^_^。
    microfactory编译示例:

  • 准备go的代码

在/home/ingresge/AP/AOSP_Q中创建一个目录hello:
创建hello.go---vim hello/hello.go
在其中打印一个“Hello,Go!”

package main

import (
"log"
"os"
)

func main() {
  testlog := log.New(os.Stderr, "", log.Ltime)
  testlog.Println("Hello,Go!")
}
  • 使用microfactory 编译hello.go

    /home/ingresge/AP/AOSP_Q/out/microfactory_Linux -pkg-path android/hello=/home/ingresge/AP/AOSP_Q/hello -trimpath /home/ingresge/AP/AOSP_Q/hello -o /home/ingresge/AP/AOSP_Q/out/hellogo android/hello/
  • 运行
    执行命令:./out/hellogo
    输出结果:

17:18:44 Hello,Go!

6.3 soong_ui

soong_ui 是通过编译 build/soong/cmd/soong_ui/main.go得来,我们接下来分析一下main.go的一些流程

6.3.1 main.go调用栈

image94c42a726a931689.png

6.3.2 soong_ui启动编译的入口

func main() {
       var stdio terminal.StdioInterface
       stdio = terminal.StdioImpl{}

       // dumpvar uses stdout, everything else should be in stderr
       if os.Args[1] == "--dumpvar-mode" || os.Args[1] == "--dumpvars-mode" {
               stdio = terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
       }

       writer := terminal.NewWriter(stdio)
       defer writer.Finish()

       log := logger.New(writer)
       defer log.Cleanup()

       if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
               os.Args[1] == "--dumpvars-mode" ||
               os.Args[1] == "--dumpvar-mode") {

               log.Fatalln("The `soong` native UI is not yet available.")
       }

       ctx, cancel := context.WithCancel(context.Background())
       defer cancel()

       trace := tracer.New(log)
       defer trace.Close()

       met := metrics.New()

       stat := &status.Status{}
       defer stat.Finish()
       stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"),
               build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
       stat.AddOutput(trace.StatusTracer())

       build.SetupSignals(log, cancel, func() {
               trace.Close()
               log.Cleanup()
               stat.Finish()
       })

       buildCtx := build.Context{ContextImpl: &build.ContextImpl{
               Context: ctx,
               Logger:  log,
               Metrics: met,
               Tracer:  trace,
               Writer:  writer,
               Status:  stat,
       }}
       var config build.Config
       if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
               config = build.NewConfig(buildCtx)
       } else {
               config = build.NewConfig(buildCtx, os.Args[1:]...)
       }

       build.SetupOutDir(buildCtx, config)

       logsDir := config.OutDir()
       if config.Dist() {
               logsDir = filepath.Join(config.DistDir(), "logs")
       }

       os.MkdirAll(logsDir, 0777)
       log.SetOutput(filepath.Join(logsDir, "soong.log"))
       trace.SetOutput(filepath.Join(logsDir, "build.trace"))
       stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
       stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))

       defer met.Dump(filepath.Join(logsDir, "build_metrics"))

       if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
               if !strings.HasSuffix(start, "N") {
                       if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
                               log.Verbosef("Took %dms to start up.",
                                       time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
                               buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
                       }
               }

               if executable, err := os.Executable(); err == nil {
                       trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
               }
       }

       // Fix up the source tree due to a repo bug where it doesn't remove
       // linkfiles that have been removed
       fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
       fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")

       f := build.NewSourceFinder(buildCtx, config)
       defer f.Shutdown()
       build.FindSources(buildCtx, config, f)

       if os.Args[1] == "--dumpvar-mode" {
               dumpVar(buildCtx, config, os.Args[2:])
       } else if os.Args[1] == "--dumpvars-mode" {
               dumpVars(buildCtx, config, os.Args[2:])
       } else {
               if config.IsVerbose() {
                       writer.Print("! The argument `showcommands` is no longer supported.")
                       writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
                       writer.Print("!")
                       writer.Print(fmt.Sprintf("!   gzip -cd %s/verbose.log.gz | less -R", logsDir))
                       writer.Print("!")
                       writer.Print("! Older versions are saved in verbose.log.#.gz files")
                       writer.Print("")
                       time.Sleep(5 * time.Second)
               }

               toBuild := build.BuildAll
               if config.Checkbuild() {
                       toBuild |= build.RunBuildTests
               }
               build.Build(buildCtx, config, toBuild)
       }
}

主要执行soong/ui/build/build.go,从build.go就可以看到执行soong的大体流程。

main.go中配置的toBuild为 BuildProductConfig | BuildSoong | BuildKati | BuildNinja,支持productconfig\soong\kati\ninja的构建。

6.3.3 Build调用栈

image4955ac59156d0073.png

编译步骤如下:

  1. runMakeProductConfig 主要配置编译参数

  2. runSoong 对工具进行编译,编译出blueprint等编译工具, 把*.bp 编译成 out/soong/build.ninja

/.minibootstrap/build.ninja
- Run minibp to generate .bootstrap/build.ninja (Primary stage) - Run minibp to generate .minibootstrap/build.ninja.in
/.bootstrap/build.ninja
- Build any bootstrap_go_binary rules and dependencies -- usually the primary builder and any build or runtime dependencies. - Run the primary builder to generate build.ninja
  1. runKatiBuild, 加载 build/make/core/main.mk, 搜集所有的Android.mk文件生成ninja文件:out/build-aosp_arm.ninja

  2. runKatiPackage, 加载build/make/packaging/main.mk, 编译生成out/build-aosp_arm-package.ninja

  3. createCombinedBuildNinjaFile,将out/soong/build.ninja 、out/build-aosp_arm.ninja和out/build-aosp_arm-package.ninja, 合成为out/combined-aosp_arm.ninja

  4. runNinja,运行Ninja命令, 解析combined-aosp_arm.ninja,执行编译过程

out/combined-aosp_arm.ninja 内容展示如下:

builddir = out
pool local_pool
 depth = 42
build _kati_always_build_: phony
subninja out/build-aosp_arm.ninja
subninja out/build-aosp_arm-package.ninja
subninja out/soong/build.ninja

6.3.4 Build

Build入口

[/build/soong/ui/build/build.go]
func Build(ctx Context, config Config, what int) {
       ctx.Verboseln("Starting build with args:", config.Arguments())
       ctx.Verboseln("Environment:", config.Environment().Environ())

       if config.SkipMake() {
               ctx.Verboseln("Skipping Make/Kati as requested")
               what = what & (BuildSoong | BuildNinja)
       }

       if inList("help", config.Arguments()) {
               help(ctx, config, what)
               return
       } else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
               clean(ctx, config, what)
               return
       }

       // Make sure that no other Soong process is running with the same output directory
       buildLock := BecomeSingletonOrFail(ctx, config)
       defer buildLock.Unlock()

       checkProblematicFiles(ctx)
       SetupOutDir(ctx, config)
       checkCaseSensitivity(ctx, config)
       ensureEmptyDirectoriesExist(ctx, config.TempDir())
       SetupPath(ctx, config)

       if config.StartGoma() {
               // Ensure start Goma compiler_proxy
               startGoma(ctx, config)
       }

       if what&BuildProductConfig != 0 {
               // Run make for product config
               runMakeProductConfig(ctx, config)
       }

       if inList("installclean", config.Arguments()) {
               installClean(ctx, config, what)
               ctx.Println("Deleted images and staging directories.")
               return
       } else if inList("dataclean", config.Arguments()) {
               dataClean(ctx, config, what)
               ctx.Println("Deleted data files.")
               return
       }

       if what&BuildSoong != 0 {
               // Run Soong
               runSoong(ctx, config)
       }

       if what&BuildKati != 0 {
               // Run ckati
               genKatiSuffix(ctx, config)
               runKatiCleanSpec(ctx, config)
               runKatiBuild(ctx, config)
               runKatiPackage(ctx, config)

               ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
       } else {
               // Load last Kati Suffix if it exists
               if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
                       ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
                       config.SetKatiSuffix(string(katiSuffix))
               }
       }

       // Write combined ninja file
       createCombinedBuildNinjaFile(ctx, config)

       if what&RunBuildTests != 0 {
               testForDanglingRules(ctx, config)
       }

       if what&BuildNinja != 0 {
               if !config.SkipMake() {
                       installCleanIfNecessary(ctx, config)
               }

               // Run ninja
               runNinja(ctx, config)
       }
}

6.4 main.mk文件分析

执行runKatiBuild时,有个重要的步骤,就是加载build/make/core/main.mk,main.mk文件是Android Build系统的主控文件。从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。Makefile文件看上去很庞大,其实主要由三种内容构成: 变量定义、函数定义和目标依赖规则,此外mk文件之间的包含也很重要。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-2.jpg

main.mk主要做了以下几件事情:

 1. 定义编译目标product

 2. 加载config.mk来初始化相关变量,检测编译环境和目标环境

 3. 清除规则,清除out目录中的dex文件

 4. 加载build/croe/definitions.mk,定义了很多通用函数,供编译过程调用

 5. 加载平台开发工具包  build/make/core/pdk_config.mk

 6. 加载系统中所有的Android.mk,最终会被存放到out/.module_paths/Android.mk.list

 7. Link 类型检查,校验Link

 8. 要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中

 9. 运行时APEX库,并进行检查校验

 10. 将所有要安装的模块都保存在变量ALL_DEFAULT_INSTALLED_MODULES中,并且将build/core/Makefie文件加载进来。build/core/Makefie文件会根据要安装的模块生成system.img、super.img、boot.img和recovery.img等镜像文件的生成规则

 11. 定义编译的image目标

 12. 构建文件,然后将其打包成rom格式

6.4.1 定义编译目标product

流程1:没有KATI命令时,走run_soong_ui执行,通过soong_ui.bash --make-mode 进行编译

流程2:通过kati命令编译时
在Android10.0中,使用的是soong构建,因此直接走流程2

确定了最终的编译目标为droid

ifndef KATI

host_prebuilts := linux-x86
ifeq ($(shell uname),Darwin)
host_prebuilts := darwin-x86
endif

#流程1:没有KATI命令时,走run_soong_ui执行,通过soong_ui.bash --make-mode 进行编译
.PHONY: run_soong_ui
run_soong_ui:
       +@prebuilts/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(MAKECMDGOALS)

.PHONY: $(MAKECMDGOALS)
$(sort $(MAKECMDGOALS)) : run_soong_ui
       @#empty

else # KATI

#流程2:通过kati命令编译时,走该流程,Android10.0中走该流程
$(info [1/1] initializing build system ...)
....

#1.定义编译目标product
#这是默认目标。它必须是第一个声明的目标。
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets

.PHONY: droid_targets
droid_targets:
...
#endif #KATI

6.4.2 加载config.mk

加载config.mk来初始化相关变量,检测编译环境和目标环境,加载clang/config.mk,配置一些编译的环境。

include build/make/core/config.mk
...
#加载out/soong/make_vars-aosp_arm.mk
include $(SOONG_MAKEVARS_MK)

#加载clang编译的一些配置
include $(BUILD_SYSTEM)/clang/config.mk
...

6.4.3 清楚规则

清除规则,清除out目录中的dex文件。

...
.PHONY: clean-dex-files
clean-dex-files:
       $(hide) find $(OUT_DIR) -name "*.dex" | xargs rm -f
       $(hide) for i in `find $(OUT_DIR) -name "*.jar" -o -name "*.apk"` ; do ((unzip -l $$i 2> /dev/null | \
                               grep -q "\.dex$$" && rm -f $$i) || continue ) ; done
       @echo "All dex files and archives containing dex files have been removed."
...

6.4.5 加载pdk_config.mk

加载 平台开发工具包

...
include build/make/core/pdk_config.mk

#为userdebug、eng和non-REL生成启用动态链接器警告
ifneq ($(TARGET_BUILD_VARIANT),user)
  ADDITIONAL_BUILD_PROPERTIES += ro.bionic.ld.warning=1
else
#只要user版本不是最终版本,就启用它。
ifneq ($(PLATFORM_VERSION_CODENAME),REL)
  ADDITIONAL_BUILD_PROPERTIES += ro.bionic.ld.warning=1
endif
endif
...

6.4.6 加载系统所有的Android.mk

加载系统中所有的Android.mk,最终会被存放到out/.module_paths/Android.mk.list。
如果环境变量ONE_SHOT_MAKEFILE的值不等于空,也就是我们执行的是mm或者mmm命令,那么就表示要编译的是特定的模块。
这些指定要编译的模块的Android.mk文件路径就保存在环境变量ONE_SHOT_MAKEFILE中,因此直接将这些Android.mk文件加载进来就获得相应的编译规则。
如果环境变量ONE_SHOT_MAKEFILE的值等于空,且dont_bother不为true,会通过out/soong/Android-aosp_arm.mk 来获得Android源代码工程下的所有Android.mk文件的路径列表,并存入到out/.module_paths/Android.mk.list 中。


...
ifneq ($(ONE_SHOT_MAKEFILE),)

#我们可能已经被带有子目录makefile的“mm” shell函数调用了。
include $(SOONG_ANDROID_MK) $(wildcard $(ONE_SHOT_MAKEFILE))
# Change CUSTOM_MODULES to include only modules that were
# defined by this makefile; this will install all of those
# modules as a side-effect.  Do this after including ONE_SHOT_MAKEFILE
# so that the modules will be installed in the same place they
# would have been with a normal make.
CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))


#帮助目标打印出安装路径
define register_module_install_path
.PHONY: GET-MODULE-INSTALL-PATH-$(1)
GET-MODULE-INSTALL-PATH-$(1):
       echo 'INSTALL-PATH: $(1) $(ALL_MODULES.$(1).INSTALLED)'
endef

SORTED_ALL_MODULES := $(sort $(ALL_MODULES))
UNIQUE_ALL_MODULES :=
$(foreach m,$(SORTED_ALL_MODULES),\
    $(if $(call streq,$(m),$(lastword $(UNIQUE_ALL_MODULES))),,\
        $(eval UNIQUE_ALL_MODULES += $(m))))
SORTED_ALL_MODULES :=

$(foreach mod,$(UNIQUE_ALL_MODULES),$(if $(ALL_MODULES.$(mod).INSTALLED),\
    $(eval $(call register_module_install_path,$(mod)))\
    $(foreach path,$(ALL_MODULES.$(mod).PATH),\
        $(eval my_path_prefix := GET-INSTALL-PATH-IN)\
        $(foreach component,$(subst /,$(space),$(path)),\
            $(eval my_path_prefix := $$(my_path_prefix)-$$(component))\
            $(eval .PHONY: $$(my_path_prefix))\
            $(eval $$(my_path_prefix): GET-MODULE-INSTALL-PATH-$(mod))))))
UNIQUE_ALL_MODULES :=

else # ONE_SHOT_MAKEFILE

ifneq ($(dont_bother),true)
FULL_BUILD := true

#包括系统中的所有makefile :out/.module_paths/Android.mk.list
subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
subdir_makefiles_total := $(words int $(subdir_makefiles) post finish)
.KATI_READONLY := subdir_makefiles_total

$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))

ifneq (,$(PDK_FUSION_PLATFORM_ZIP)$(PDK_FUSION_PLATFORM_DIR))

# 加载pdk_fusion_modules.mk
include $(BUILD_SYSTEM)/pdk_fusion_modules.mk
endif # PDK_FUSION_PLATFORM_ZIP || PDK_FUSION_PLATFORM_DIR

droid_targets : blueprint_tools

endif # dont_bother

endif # ONE_SHOT_MAKEFILE
...

6.4.7 Link检查

编译时的Link 类型检查,校验Link

...

#Link 类型检查
#“ALL_LINK_TYPES”包含所有链接类型前缀的列表(通常每个模块一个,但是apk可以“链接”到java和本机代码)。
#链接类型前缀由intermediates dir所需的所有信息组成:
#
#  LINK_TYPE:TARGET:_:2ND:STATIC_LIBRARIES:libfoo
#
#所有未在“允许”或“警告”中列出的依赖关系链接类型都将成为错误
link_type_error :=

define link-type-prefix-base
$(word 2,$(subst :,$(space),$(1)))
endef
define link-type-prefix
$(if $(filter AUX-%,$(link-type-prefix-base)),$(patsubst AUX-%,AUX,$(link-type-prefix-base)),$(link-type-prefix-base))
endef
define link-type-aux-variant
$(if $(filter AUX-%,$(link-type-prefix-base)),$(patsubst AUX-%,%,$(link-type-prefix-base)))
endef
define link-type-common
$(patsubst _,,$(word 3,$(subst :,$(space),$(1))))
endef
define link-type-2ndarchprefix
$(patsubst _,,$(word 4,$(subst :,$(space),$(1))))
endef
define link-type-class
$(word 5,$(subst :,$(space),$(1)))
endef
define link-type-name
$(word 6,$(subst :,$(space),$(1)))
endef
define link-type-os
$(strip $(eval _p := $(link-type-prefix))\
  $(if $(filter HOST HOST_CROSS,$(_p)),\
    $($(_p)_OS),\
    $(if $(filter AUX,$(_p)),AUX,android)))
endef
define link-type-arch
$($(link-type-prefix)_$(link-type-2ndarchprefix)ARCH)
endef
define link-type-name-variant
$(link-type-name) ($(link-type-class) $(link-type-os)-$(link-type-arch))
endef

...
# 验证$(1)是否可以链接到$(2)
# $(1)和$(2)都是上面定义的链接类型前缀
define verify-link-type
$(foreach t,$($(2).TYPE),\
  $(if $(filter-out $($(1).ALLOWED),$(t)),\
    $(if $(filter $(t),$($(1).WARN)),\
      $(call link-type-warning,$(1),$(2),$(t)),\
      $(call link-type-error,$(1),$(2),$(t)))))
endef

#验证所有分支/配置都有合理的警告/错误,并删除此重写
verify-link-type = $(eval $$(1).MISSING := true)
...

6.4.8 加载product_config.mk

要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中

...
#列出特定产品安装的大多数文件,包括:
# - PRODUCT_PACKAGES, and their LOCAL_REQUIRED_MODULES
# - PRODUCT_COPY_FILES
# 要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中
define product-installed-files
  $(eval _mk := $(strip $(1))) \
  $(eval _pif_modules := \
    $(PRODUCTS.$(_mk).PRODUCT_PACKAGES) \
    $(if $(filter eng,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_ENG)) \
    $(if $(filter debug,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_DEBUG)) \
    $(if $(filter tests,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_TESTS)) \
    $(if $(filter asan,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_DEBUG_ASAN)) \
    $(call auto-included-modules) \
  ) \
  $(eval ### Filter out the overridden packages and executables before doing expansion) \
  $(eval _pif_overrides := $(call module-overrides,$(_pif_modules))) \
  $(eval _pif_modules := $(filter-out $(_pif_overrides), $(_pif_modules))) \
  $(eval ### Resolve the :32 :64 module name) \
  $(eval _pif_modules_32 := $(patsubst %:32,%,$(filter %:32, $(_pif_modules)))) \
  $(eval _pif_modules_64 := $(patsubst %:64,%,$(filter %:64, $(_pif_modules)))) \
  $(eval _pif_modules_rest := $(filter-out %:32 %:64,$(_pif_modules))) \
  $(eval ### Note for 32-bit product, 32 and 64 will be added as their original module names.) \
  $(eval _pif_modules := $(call get-32-bit-modules-if-we-can, $(_pif_modules_32))) \
  $(eval _pif_modules += $(_pif_modules_64)) \
  $(eval ### For the rest we add both) \
  $(eval _pif_modules += $(call get-32-bit-modules, $(_pif_modules_rest))) \
  $(eval _pif_modules += $(_pif_modules_rest)) \
  $(call expand-required-modules,_pif_modules,$(_pif_modules),$(_pif_overrides)) \
  $(filter-out $(HOST_OUT_ROOT)/%,$(call module-installed-files, $(_pif_modules))) \
  $(call resolve-product-relative-paths,\
    $(foreach cf,$(PRODUCTS.$(_mk).PRODUCT_COPY_FILES),$(call word-colon,2,$(cf))))
endef
...

6.4.9 运行APEX库

运行时APEX库,并进行检查校验


...
APEX_MODULE_LIBS := \
  libadbconnection.so \
  libadbconnectiond.so \
  libandroidicu.so \
  libandroidio.so \
  libart-compiler.so \
  libart-dexlayout.so \
  libart-disassembler.so \
  libart.so \
  libartbase.so \
  libartbased.so \
  libartd-compiler.so \
  libartd-dexlayout.so \
  libartd.so \
  libartpalette.so \
  libc.so \
  libdexfile.so \
  libdexfile_external.so \
  libdexfiled.so \
  libdexfiled_external.so \
  libdl.so \
  libdt_fd_forward.so \
  libdt_socket.so \
  libicui18n.so \
  libicuuc.so \
  libjavacore.so \
  libjdwp.so \
  libm.so \
  libnativebridge.so \
  libnativehelper.so \
  libnativeloader.so \
  libnpt.so \
  libopenjdk.so \
  libopenjdkjvm.so \
  libopenjdkjvmd.so \
  libopenjdkjvmti.so \
  libopenjdkjvmtid.so \
  libpac.so \
  libprofile.so \
  libprofiled.so \
  libsigchain.so \
# Conscrypt APEX libraries
APEX_MODULE_LIBS += \
  libjavacrypto.so \
...
#如果下面的检查失败,那么某些库已经在system/lib或system/lib64中结束,而这些库只打算放入一些APEX包中。
#可能的原因是/system中的库或二进制文件已经增长了一个直接或间接拉入禁止的库的依赖关系。
#要解决此问题,请查找库所属的APEX包-在“APEX”构建模块中的“native_shared_lib”属性中搜索它(参见art/build/APEX/安卓.bp例如)。
#然后检查APEX包中是否有应该使用的导出库,即在其“native_shared_lib”属性中列出的库,对应的“cc_library”模块具有“stubs”子句(如art/libdexfile中的libdexfile_external)/安卓.bp).
#如果您找不到适合您需要的APEX导出库,或者您认为/system中应该允许您依赖的库,那么请与包含该库的APEX包的所有者联系。
#如果在APEX中导出的库出现此错误,则APEX可能配置错误,或者生成系统中出现错误。
#请联系APEX包主和/或soong-team@,或android-building@googlegroups.com外部的。
  define check-apex-libs-absence
    $(call maybe-print-list-and-error, \
      $(filter $(foreach lib,$(APEX_MODULE_LIBS),%/$(lib)), \
        $(filter-out $(foreach dir,$(APEX_LIBS_ABSENCE_CHECK_EXCLUDE), \
                       $(TARGET_OUT)/$(if $(findstring %,$(dir)),$(dir),$(dir)/%)), \
          $(filter $(TARGET_OUT)/lib/% $(TARGET_OUT)/lib64/%,$(1)))), \
      APEX libraries found in system image (see comment for check-apex-libs-absence in \
      build/make/core/main.mk for details))
  endef
  # TODO(b/129006418): The check above catches libraries through product
  # dependencies visible to make, but as long as they have install rules in
  # /system they may still be created there through other make targets. To catch
  # that we also do a check on disk just before the system image is built.
  define check-apex-libs-absence-on-disk
    $(hide) ( \
      cd $(TARGET_OUT) && \
      findres=$$(find lib* \
        $(foreach dir,$(APEX_LIBS_ABSENCE_CHECK_EXCLUDE),-path "$(subst %,*,$(dir))" -prune -o) \
        -type f \( -false $(foreach lib,$(APEX_MODULE_LIBS),-o -name $(lib)) \) \
        -print) && \
      if [ -n "$$findres" ]; then \
        echo "APEX libraries found in system image (see comment for check-apex-libs-absence" 1>&2; \
        echo "in build/make/core/main.mk for details):" 1>&2; \
        echo "$$findres" | sort 1>&2; \
        false; \
      fi; \
    )
  endef
endif
...

6.4.10 保存所有模块

将所有要安装的模块都保存在变量ALL_DEFAULT_INSTALLED_MODULES中,并且将build/core/Makefie文件加载进来。

... 
#build/core/Makefie文件会根据要安装的模块生成system.img、super.img、boot.img和recovery.img等镜像文件的生成规则
# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES
# and get rid of it from this list.
modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_target_FILES) \
    $(product_host_FILES) \
    $(call get-tagged-modules,$(tags_to_install)) \
    $(CUSTOM_MODULES) \
  )
...
#build/make/core/Makefile包含了我们不想污染这个顶级Makefile的额外内容。
#它希望“ALL_DEFAULT_INSTALLED_MODULES”包含当前make期间构建的所有模块,但它还进一步扩展了“ALL_DEFAULT_INSTALLED_MODULES”。
ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
include $(BUILD_SYSTEM)/Makefile
modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
ALL_DEFAULT_INSTALLED_MODULES :=

...
#这是用来获得正确的秩序,你也可以使用这些,但他们被认为是没有文件,所以不要抱怨,如果他们的行为改变。
#依赖于所有复制头的内部目标(请参见复制_标题.make). 需要首先复制头的其他目标可以依赖于此目标。
.PHONY: all_copied_headers
all_copied_headers: ;

$(ALL_C_CPP_ETC_OBJECTS): | all_copied_headers

# All the droid stuff, in directories
.PHONY: files
files: $(modules_to_install) \
       $(INSTALLED_ANDROID_INFO_TXT_TARGET)
...

6.4.11 定义编译的image目标

定义了我们编译过程中的所有image目标

...
.PHONY: ramdisk
ramdisk: $(INSTALLED_RAMDISK_TARGET)

.PHONY: ramdisk_debug
ramdisk_debug: $(INSTALLED_DEBUG_RAMDISK_TARGET)

.PHONY: systemtarball
systemtarball: $(INSTALLED_SYSTEMTARBALL_TARGET)

.PHONY: boottarball
boottarball: $(INSTALLED_BOOTTARBALL_TARGET)

.PHONY: userdataimage
userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET)

ifneq (,$(filter userdataimage, $(MAKECMDGOALS)))
$(call dist-for-goals, userdataimage, $(BUILT_USERDATAIMAGE_TARGET))
endif

.PHONY: userdatatarball
userdatatarball: $(INSTALLED_USERDATATARBALL_TARGET)

.PHONY: cacheimage
cacheimage: $(INSTALLED_CACHEIMAGE_TARGET)

.PHONY: bptimage
bptimage: $(INSTALLED_BPTIMAGE_TARGET)

.PHONY: vendorimage
vendorimage: $(INSTALLED_VENDORIMAGE_TARGET)

.PHONY: productimage
productimage: $(INSTALLED_PRODUCTIMAGE_TARGET)

.PHONY: productservicesimage
productservicesimage: $(INSTALLED_PRODUCT_SERVICESIMAGE_TARGET)

.PHONY: odmimage
odmimage: $(INSTALLED_ODMIMAGE_TARGET)

.PHONY: systemotherimage
systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)

.PHONY: superimage_empty
superimage_empty: $(INSTALLED_SUPERIMAGE_EMPTY_TARGET)

.PHONY: bootimage
bootimage: $(INSTALLED_BOOTIMAGE_TARGET)

.PHONY: bootimage_debug
bootimage_debug: $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)

.PHONY: vbmetaimage
vbmetaimage: $(INSTALLED_VBMETAIMAGE_TARGET)

.PHONY: auxiliary
auxiliary: $(INSTALLED_AUX_TARGETS)
...

6.4.12 构建系统,打包rom

构建文件,然后将其打包成rom格式

...
.PHONY: droidcore
droidcore: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
    $(INSTALLED_SYSTEMIMAGE_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
    $(INSTALLED_BOOTIMAGE_TARGET) \
    $(INSTALLED_DEBUG_RAMDISK_TARGET) \
    $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
    $(INSTALLED_RECOVERYIMAGE_TARGET) \
    $(INSTALLED_VBMETAIMAGE_TARGET) \
    $(INSTALLED_USERDATAIMAGE_TARGET) \
    $(INSTALLED_CACHEIMAGE_TARGET) \
    $(INSTALLED_BPTIMAGE_TARGET) \
    $(INSTALLED_VENDORIMAGE_TARGET) \
    $(INSTALLED_ODMIMAGE_TARGET) \
    $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
    $(INSTALLED_PRODUCTIMAGE_TARGET) \
    $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
    $(INSTALLED_FILES_FILE) \
    $(INSTALLED_FILES_JSON) \
    $(INSTALLED_FILES_FILE_VENDOR) \
    $(INSTALLED_FILES_JSON_VENDOR) \
    $(INSTALLED_FILES_FILE_ODM) \
    $(INSTALLED_FILES_JSON_ODM) \
    $(INSTALLED_FILES_FILE_PRODUCT) \
    $(INSTALLED_FILES_JSON_PRODUCT) \
    $(INSTALLED_FILES_FILE_PRODUCT_SERVICES) \
    $(INSTALLED_FILES_JSON_PRODUCT_SERVICES) \
    $(INSTALLED_FILES_FILE_SYSTEMOTHER) \
    $(INSTALLED_FILES_JSON_SYSTEMOTHER) \
    $(INSTALLED_FILES_FILE_RAMDISK) \
    $(INSTALLED_FILES_JSON_RAMDISK) \
    $(INSTALLED_FILES_FILE_DEBUG_RAMDISK) \
    $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \
    $(INSTALLED_FILES_FILE_ROOT) \
    $(INSTALLED_FILES_JSON_ROOT) \
    $(INSTALLED_FILES_FILE_RECOVERY) \
    $(INSTALLED_FILES_JSON_RECOVERY) \
    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
    auxiliary \
    soong_docs
...

#构建一个完整的系统——默认情况下是构建droidcore
droid_targets: droidcore dist_files
...

7. 总结

至此,make的流程我们基本理清了,后面还有ninja是如何把build.ninja编译出来的,image如何打包的,我们后面继续分析。


Linux命令--将文件打包、压缩并分割成制定大小

0 条评论 学习总结 无标签 adtxl

将大文件或目录打包、 压缩并分割成制定大小的文件,在Linux下可以通过组合使用tar,bzip2(或者gzip),split命令来实现。

命令格试 tar -zcvf [ file_directory ] |sqlit -b [ file_size ][ m,k ] - [ file.tar.gz ]

将file目录的文件压缩并分割成每个大小为4G文件

shell > tar -zcvf file_name |split -b 4096m - file_name.tar.gz

shell > ls

-rw-r--r-- 1 root root 4294967296 Mar 9 10:40 file_name.tar.gzaa
-rw-r--r-- 1 root root 4294967296 Mar 9 10:48 file_name.tar.gzab
-rw-r--r-- 1 root root 2282762240 Mar 9 10:52 file_name.tar.gzac

cat file_name.tar.gza* |tar zxv
解释:
用cat来读所有的压缩包,利用tar来进行解压

https://blog.csdn.net/nantongcjq/article/details/78979787


Android Service Framework、Binder Driver相关术语

0 条评论 学习总结 无标签 adtxl

相关术语:

  • 服务管理器(Service Server):指运行系统服务的进程,如System Server, Media Server

  • 服务客户端(Server Client):指使用系统服务的进程

  • 上下文管理器(Context Manager):是一个管理系统服务的系统进程,它管理安装在系统中的各种系统服务的位置信息Handle, 这些Handle用来指定Binder IPC的目的地址。

  • 服务框架(Service Framework):包含前面提到的Service Manager,其中定义了一系列类,用于服务使用者与系统服务间的RPC操作。

  • 服务接口(Service Interface):它是一个预先定义的接口,用在服务使用者与系统服务间。系统服务应该根据相关接口实现Stub函数,并提供相关服务。而服务使用者也必须根据相关接口调用服务。

  • 服务使用者:在服务客户进程中实际使用服务的模块。

  • 服务(Service):由服务Stub函数实现定义在服务接口中的功能,是提供实际服务功能的模块。

  • 服务代理(Service Proxy):执行RPC时用来对数据进行Marshalling处理的对象,不同的服务接口对应不同的服务代理。它提供服务代码函数,根据服务接口中定义的函数,对数据分别进行不同的Marshalling处理。

  • 服务Stub:RPC执行时用来对数据进行UnMarshalling处理的对象,该对象随接口不同而不同。它对接收到的数据进行UnMarshalling处理后,调用相关的服务Stub函数

  • Binder Driver:Binder是Android中为支持IPC而采用的机制,它以Android Linux内核的Device Driver形态存在。

  • Binder IPC:它是Android中进程间通过Binder Driver交换数据的方式。

  • Binder IPC数据:一种用在Service Framework与Binder Driver间的数据格式。

  • Binder RPC:服务会向使用者提供基于特定服务接口的函数,服务使用者通过Binder IPC调用这些函数,就像调用自身函数一样。Binder IPC内部是基于Binder IPC机制的。

  • Binder RPC数据:服务使用者与服务间进行Binder IPC时的数据。


thermal代码分析(2)thermal_core.c

0 条评论 学习总结 无标签 adtxl

thermal_core是thermal框架的核心,负责将thermal_zone、thermal_cooling和thermal_governor连接起来
首先看下头文件thermal_core.h,

/* Initial state of a cooling device during binding */
#define THERMAL_NO_TARGET -1UL

/*
 * This structure is used to describe the behavior of
 * a certain cooling device on a certain trip point
 * in a certain thermal zone
 */
 // thermal_instance结构体定义
 struct thermal_instance{
 ......
 };
// 注册和卸载governor
int thermal_register_governor(struct thermal_governor *);
void thermal_unregister_governor(struct thermal_governor *);

// 下面通过宏判断使用哪个governor
#ifdef CONFIG_THERMAL_GOV_STEP_WISE
int thermal_gov_step_wise_register(void);
void thermal_gov_step_wise_unregister(void);
#else
static inline int thermal_gov_step_wise_register(void) { return 0; }
static inline void thermal_gov_step_wise_unregister(void) {}
#endif /* CONFIG_THERMAL_GOV_STEP_WISE */
...
...
/* device tree support */
#ifdef CONFIG_THERMAL_OF
int of_parse_thermal_zones(void);
void of_thermal_destroy_zones(void);
int of_thermal_get_ntrips(struct thermal_zone_device *);
bool of_thermal_is_trip_valid(struct thermal_zone_device *, int);
const struct thermal_trip *
of_thermal_get_trip_points(struct thermal_zone_device *);
#else
static inline int of_parse_thermal_zones(void) { return 0; }
static inline void of_thermal_destroy_zones(void) { }
static inline int of_thermal_get_ntrips(struct thermal_zone_device *tz)
{
    return 0;
}
static inline bool of_thermal_is_trip_valid(struct thermal_zone_device *tz,
                        int trip)
{
    return false;
}
static inline const struct thermal_trip *
of_thermal_get_trip_points(struct thermal_zone_device *tz)
{
    return NULL;
}
#endif

#endif /* __THERMAL_CORE_H__ */

在thermal_core.c除了以上函数的具体实现,还包括一些其它函数,首先看一下thermal的初始化函数thermal_init()函数:

static int __init thermal_init(void)
{
    int result;

    result = thermal_register_governors();  // 注册governor
    if (result)
        goto error;

    result = class_register(&thermal_class);    // 注册/sys/class/thermal
    if (result)
        goto unregister_governors;

    result = genetlink_init();      // generic netlink初始化,干嘛的?
    if (result)
        goto unregister_class;

    result = of_parse_thermal_zones();  // 解析dts文件中的“thermal-zone”节点,并注册thermal_zone_device
    if (result)
        goto exit_netlink;

    result = register_pm_notifier(&thermal_pm_nb);  // 注册notifier
    if (result)
        pr_warn("Thermal: Can not register suspend notifier, return %d\n",
            result);

    return 0;

exit_netlink:
    genetlink_exit();
unregister_class:
    class_unregister(&thermal_class);
unregister_governors:
    thermal_unregister_governors();
error:
    idr_destroy(&thermal_tz_idr);
    idr_destroy(&thermal_cdev_idr);
    mutex_destroy(&thermal_idr_lock);
    mutex_destroy(&thermal_list_lock);
    mutex_destroy(&thermal_governor_lock);
    return result;
}

接下来,看一下of_parse_thermal_zones函数,我们从设备树文件中找个thermal-zones节点,如下所示

thermal-zones {
        soc_thermal {
            polling-delay = <1000>;
            polling-delay-passive = <100>;
            sustainable-power = <2150>;

            thermal-sensors = <&aml_sensor0 3>;

            trips {
                switch_on: trip-point@0 {
                    temperature = <70000>;
                    hysteresis = <1000>;
                    type = "passive";
                };
                control: trip-point@1 {
                    temperature = <80000>;
                    hysteresis = <1000>;
                    type = "passive";
                };
                hot: trip-point@2 {
                    temperature = <85000>;
                    hysteresis = <5000>;
                    type = "hot";
                };
                critical: trip-point@3 {
                    temperature = <260000>;
                    hysteresis = <1000>;
                    type = "critical";
                };
            };

            cooling-maps {
                cpufreq_cooling_map {
                    trip = <&control>;
                    cooling-device = <&cpus 0 4>;
                    contribution = <1024>;
                };
                cpucore_cooling_map {
                    trip = <&control>;
                    cooling-device = <&cpu_cluster0 0 3>;
                    contribution = <1024>;
                };
                gpufreq_cooling_map {
                    trip = <&control>;
                    cooling-device = <&gpu 0 4>;
                    contribution = <1024>;
                };
                gpucore_cooling_map {
                    trip = <&control>;
                    cooling-device = <&gpucore 0 2>;
                    contribution = <1024>;
                };
            };
        };
    };

}; /* end of / */

再看一下of_parse_thermal_zones函数的具体实现,

/**
 * of_parse_thermal_zones - parse device tree thermal data
 *
 * Initialization function that can be called by machine initialization
 * code to parse thermal data and populate the thermal framework
 * with hardware thermal zones info. This function only parses thermal zones.
 * Cooling devices and sensor devices nodes are supposed to be parsed
 * by their respective drivers.
 *
 * Return: 0 on success, proper error code otherwise
 *
 */
int __init of_parse_thermal_zones(void)
{
    struct device_node *np, *child;
    struct __thermal_zone *tz;
    struct thermal_zone_device_ops *ops;

    np = of_find_node_by_name(NULL, "thermal-zones");
    if (!np) {
        pr_debug("unable to find thermal zones\n");
        return 0; /* Run successfully on systems without thermal DT */
    }

    for_each_available_child_of_node(np, child) {
        struct thermal_zone_device *zone;
        struct thermal_zone_params *tzp;
    #ifdef CONFIG_AMLOGIC_TEMP_SENSOR
        const char *str;
    #endif
        int i, mask = 0;
        u32 prop;

        tz = thermal_of_build_thermal_zone(child);
        if (IS_ERR(tz)) {
            pr_err("failed to build thermal zone %s: %ld\n",
                   child->name,
                   PTR_ERR(tz));
            continue;
        }

        ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL);
        if (!ops)
            goto exit_free;

        tzp = kzalloc(sizeof(*tzp), GFP_KERNEL);
        if (!tzp) {
            kfree(ops);
            goto exit_free;
        }

        /* No hwmon because there might be hwmon drivers registering */
        tzp->no_hwmon = true;

        if (!of_property_read_u32(child, "sustainable-power", &prop))
            tzp->sustainable_power = prop;

    #ifdef CONFIG_AMLOGIC_TEMP_SENSOR
        if (!of_property_read_string(child, "policy", &str))
            strncpy(tzp->governor_name, str, THERMAL_NAME_LENGTH);
    #endif

        for (i = 0; i < tz->ntrips; i++)
            mask |= 1 << i;

        /* these two are left for temperature drivers to use */
        tzp->slope = tz->slope;
        tzp->offset = tz->offset;
// 注册thermal_zone_device
        zone = thermal_zone_device_register(child->name, tz->ntrips,
                            mask, tz,
                            ops, tzp,
                            tz->passive_delay,
                            tz->polling_delay);
        if (IS_ERR(zone)) {
            pr_err("Failed to build %s zone %ld\n", child->name,
                   PTR_ERR(zone));
            kfree(tzp);
            kfree(ops);
            of_thermal_free_zone(tz);
            /* attempting to build remaining zones still */
        }
    }
    of_node_put(np);

    return 0;

exit_free:
    of_node_put(child);
    of_node_put(np);
    of_thermal_free_zone(tz);

    /* no memory available, so free what we have built */
    of_thermal_destroy_zones();

    return -ENOMEM;
}

在解析设备树函数中,将会调用thermal_zone_device设备注册函数,


thermal代码分析(1)Makefile文件

0 条评论 学习总结 thermal adtxl
#
# Makefile for sensor chip drivers.
#

obj-$(CONFIG_THERMAL)       += thermal_sys.o
thermal_sys-y           += thermal_core.o               #核心代码

# interface to/from other layers providing sensors
thermal_sys-$(CONFIG_THERMAL_HWMON)     += thermal_hwmon.o
thermal_sys-$(CONFIG_THERMAL_OF)        += of-thermal.o         #dts解析

# governors                 五种governor
thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE)    += fair_share.o
thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE)    += user_space.o
thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR)   += power_allocator.o

# cpufreq cooling
thermal_sys-$(CONFIG_CPU_THERMAL)   += cpu_cooling.o

# clock cooling
thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o

# devfreq cooling
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o

# platform thermal drivers      平台相关代码,提供读取温度的接口
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)  += qcom-spmi-temp-alarm.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL)  += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL)  += rcar_thermal.o
obj-$(CONFIG_KIRKWOOD_THERMAL)  += kirkwood_thermal.o
obj-y               += samsung/
obj-$(CONFIG_DOVE_THERMAL)      += dove_thermal.o
obj-$(CONFIG_DB8500_THERMAL)    += db8500_thermal.o
obj-$(CONFIG_ARMADA_THERMAL)    += armada_thermal.o
obj-$(CONFIG_TANGO_THERMAL) += tango_thermal.o
obj-$(CONFIG_IMX_THERMAL)   += imx_thermal.o
obj-$(CONFIG_MAX77620_THERMAL)  += max77620_thermal.o
obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o
obj-$(CONFIG_DB8500_CPUFREQ_COOLING)    += db8500_cpufreq_cooling.o
obj-$(CONFIG_INTEL_POWERCLAMP)  += intel_powerclamp.o
obj-$(CONFIG_X86_PKG_TEMP_THERMAL)  += x86_pkg_temp_thermal.o
obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE)   += intel_soc_dts_iosf.o
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL)   += intel_quark_dts_thermal.o
obj-$(CONFIG_TI_SOC_THERMAL)    += ti-soc-thermal/
obj-$(CONFIG_INT340X_THERMAL)  += int340x_thermal/
obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o
obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o
obj-$(CONFIG_ST_THERMAL)    += st/
obj-$(CONFIG_QCOM_TSENS)    += qcom/
obj-$(CONFIG_TEGRA_SOCTHERM)    += tegra/
obj-$(CONFIG_HISI_THERMAL)     += hisi_thermal.o
obj-$(CONFIG_MTK_THERMAL)   += mtk_thermal.o
obj-$(CONFIG_GENERIC_ADC_THERMAL)   += thermal-generic-adc.o

Android Binder机制(1)简介

0 条评论 学习总结 Android binder adtxl

1. 概述

Android系统中,每个应用程序是由Android的Activity,Service,Broadcast,ContentProvider这四剑客的中一个或多个组合而成,这四剑客所涉及的多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。不仅于此,整个Android系统架构中,大量采用了Binder机制作为IPC(进程间通信)方案,当然也存在部分其他的IPC方式,比如Zygote通信便是采用socket。

Binder作为Android系统提供的一种IPC机制,无论从事系统开发还是应用开发,都应该有所了解,这是Android系统中最重要的组成,也是最难理解的一块知识点,错综复杂。要深入了解Binder机制,最好的方法便是阅读源码,借用Linux鼻祖Linus Torvalds曾说过的一句话:Read The Fucking Source Code。

2. Binder

2.1 IPC原理

从进程角度来看IPC机制

每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。

2.2 Binder原理

Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动,其中Service Manager用于管理系统中的各种服务。架构图如下所示:

可以看出无论是注册服务和获取服务的过程都需要ServiceManager,需要注意的是此处的Service Manager是指Native层的ServiceManager(C++),并非指framework层的ServiceManager(Java)。

ServiceManager是整个Binder通信机制的大管家,是Android进程间通信机制Binder的守护进程,要掌握Binder机制,首先需要了解系统是如何首次启动Service Manager。当Service Manager启动之后,Client端和Server端通信时都需要先获取Service Manager接口,才能开始通信服务。

图中Client/Server/ServiceManage之间的相互通信都是基于Binder机制。既然基于Binder机制通信,那么同样也是C/S架构,则图中的3大步骤都有相应的Client端与Server端。

  1. 注册服务(addService):Server进程要先注册Service到ServiceManager。该过程:Server是客户端,ServiceManager是服务端。
  2. 获取服务(getService):Client进程使用某个Service前,须先向ServiceManager中获取相应的Service。该过程:Client是客户端,ServiceManager是服务端。
  3. 使用服务:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:client是客户端,server是服务端。

图中的Client,Server,Service Manager之间交互都是虚线表示,是由于它们彼此之间不是直接交互的,而是都通过与Binder驱动进行交互的,从而实现IPC通信方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager可以看做是Android平台的基础架构,而Client和Server是Android的应用层,开发人员只需自定义实现client、Server端,借助Android的基本平台架构便可以直接进行IPC通信。

2.3 C/S模式

BpBinder(客户端)和BBinder(服务端)都是Android中Binder通信相关的代表,它们都从IBinder类中派生而来,关系图如下:

  • client端:BpBinder.transact()来发送事务请求;
  • server端:BBinder.onTransact()会接收到相应事务。

转载:
http://gityuan.com/2015/10/31/binder-prepare/


Linux内核中断机制

0 条评论 学习总结 Linux Linux 中断 adtxl

1. 中断的概念

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。

当 CPU 收到一个中断 (IRQ)的时候,会去执行该中断对应的处理函数(ISR)。普通情况下,会有一个中断向量表,向量表中定义了 CPU 对应的每一个外设资源的中断处理程序的入口,当发生对应的中断的时候, CPU 直接跳转到这个入口执行程序。也就是中断上下文。(注意:中断上下文中,不可阻塞睡眠)。

2. Linux中断 top/bottom

玩过 MCU 的人都知道,中断服务程序的设计最好是快速完成任务并退出,因为此刻系统处于被中断中。但是在 ISR 中又有一些必须完成的事情,比如:清中断标志,读/写数据,寄存器操作等。

在 Linux 中,同样也是这个要求,希望尽快的完成 ISR。但事与愿违,有些 ISR 中任务繁重,会消耗很多时间,导致响应速度变差。Linux 中针对这种情况,将中断分为了两部分:

  1. 上半部(top half):收到一个中断,立即执行,有严格的时间限制,只做一些必要的工作,比如:应答,复位等。这些工作都是在所有中断被禁止的情况下完成的。

  2. 底半部(bottom half):能够被推迟到后面完成的任务会在底半部进行。在适合的时机,下半部会被开中断执行。

3. 中断处理程序

驱动程序可以使用接口:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
参数 含义
irq 表了该中断的中断号,一般 CPU 的中断号都会事先定义好。
handler 中断发生后的 ISR
flags 中断标志( IRQF_DISABLED / IRQFSAMPLE_RANDOM / IRQF_TIMER / IRQF_SHARED)
name 中断相关的设备 ASCII 文本,例如 "keyboard",这些名字会在 /proc/irq 和 /proc/interrupts 文件使用
dev 用于共享中断线,传递驱动程序的设备结构。非共享类型的中断,直接设置成为 NULL

中断标志flag的含义:

标志 含义
IRQF_DISABLED 设置这个标志的话,意味着内核在处理这个 ISR 期间,要禁止其他中断(多数情况不使用这个)
IRQFSAMPLE_RANDOM 表明这个设备产生的中断对内核熵池有贡献
IRQF_TIMER 为系统定时器准备的标志
IRQF_SHARED 表明多个中断处理程序之间共享中断线。同一个给定的线上注册每个处理程序,必须设置这个

调用request_irq成功执行返回0。常见错误是 -EBUSY,表示给定的中断线已经在使用(或者没有指定 IRQF_SHARED)
注意:request_irq 函数可能引起睡眠,所以不允许在中断上下文或者不允许睡眠的代码中调用。

Linux内核熵池:Linux内核采用熵来描述数据的随机性。熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大。在信息学中,熵被用来表征一个符号或系统的不确定性,熵越大,表明系统所含有用信息量越少,不确定度越大。
计算机本身是可预测的系统,因此,用计算机算法不可能产生真正的随机数。但是机器的环境中充满了各种各样的噪声,如硬件设备发生中断的时间,用户点击鼠标的时间间隔等是完全随机的,事先无法预测。Linux内核实现的随机数产生器正是利用系统中的这些随机噪声来产生高质量随机数序列。
核维护了一个熵池用来收集来自设备驱动程序和其它来源的环境噪音。理论上,熵池中的数据是完全随机的,可以实现产生真随机数序列。为跟踪熵池中数据的随机性,内核在将数据加入池的时候将估算数据的随机性,这个过程称作熵估算。熵估算值描述池中包含的随机数位数,其值越大表示池中数据的随机性越好。

释放中断:

const void *free_irq(unsigned int irq, void *dev_id)

用于释放中断处理函数。

注意:Linux 中的中断处理程序是无须重入的。当给定的中断处理程序正在执行的时候,其中断线在所有的处理器上都会被屏蔽掉,以防在同一个中断线上又接收到另一个新的中断。通常情况下,除了该中断的其他中断都是打开的,也就是说其他的中断线上的重点都能够被处理,但是当前的中断线总是被禁止的,故,同一个中断处理程序是绝对不会被自己嵌套的。

4. 中断上下文

与进程上下文不一样,内核执行中断服务程序的时候,处于中断上下文。中断处理程序并没有自己的独立的栈,而是使用了内核栈,其大小一般是有限制的(32bit 机器 8KB)。所以其必须短小精悍。同时中断服务程序是打断了正常的程序流程,这一点上也必须保证快速的执行。同时中断上下文中是不允许睡眠,阻塞的。

中断上下文不能睡眠的原因是:
1、 中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在 中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没 有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死。

2、schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);

但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。

3、内核中schedule()函数本身在进来的时候判断是否处于中断上下文:

if(unlikely(in_interrupt()))

BUG();

因此,强行调用schedule()的结果就是内核BUG。

4、中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌。

5、处于中断context时候,内核是不可抢占的。因此,如果休眠,则内核一定挂起。

5. 举例

比如 RTC 驱动程序 (drivers/char/rtc.c)。在 RTC 驱动的初始化阶段,会调用到 rtc_init 函数:
module_init(rtc_init);
在这个初始化函数中调用到了 request_irq 用于申请中断资源,并注册服务程序:

static int __init rtc_init(void)
{
...
    rtc_int_handler_ptr = rtc_interrupt;
...
    request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)
...
}

RTC_IRQ 是中断号,和处理器绑定。
rtc_interrupt 是中断处理程序:

static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
    /*
     *  Can be an alarm interrupt, update complete interrupt,
     *  or a periodic interrupt. We store the status in the
     *  low byte and the number of interrupts received since
     *  the last read in the remainder of rtc_irq_data.
     */

    spin_lock(&rtc_lock);
    rtc_irq_data += 0x100;
    rtc_irq_data &= ~0xff;
    if (is_hpet_enabled()) {
        /*
         * In this case it is HPET RTC interrupt handler
         * calling us, with the interrupt information
         * passed as arg1, instead of irq.
         */
        rtc_irq_data |= (unsigned long)irq & 0xF0;
    } else {
        rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
    }

    if (rtc_status & RTC_TIMER_ON)
        mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);

    spin_unlock(&rtc_lock);

    wake_up_interruptible(&rtc_wait);

    kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);

    return IRQ_HANDLED;
}

每次收到 RTC 中断,就会调用进这个函数。

6. 中断处理流程

发生中断时,CPU执行异常向量vector_irq的代码, 即异常向量表中的中断异常的代码,它是一个跳转指令,跳去执行真正的中断处理程序,在vector_irq里面,最终会调用中断处理的总入口函数。

C 语言的入口为 : asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    handle_IRQ(irq, regs);
}

该函数的入参 irq 为中断号。

asm_do_IRQ -> handle_IRQ

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    __handle_domain_irq(NULL, irq, false, regs);
}

handleIRQ ->\_handle_domain_irq

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;

    irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);
#endif

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        generic_handle_irq(irq);
    }

    irq_exit();
    set_irq_regs(old_regs);
    return ret;
}

这里请注意:

先调用了 irq_enter 标记进入了硬件中断:

irq_enter是更新一些系统的统计信息,同时在__irq_enter宏中禁止了进程的抢占。虽然在产生IRQ时,ARM会自动把CPSR中的I位置位,禁止新的IRQ请求,直到中断控制转到相应的流控层后才通过local_irq_enable()打开。那为何还要禁止抢占?这是因为要考虑中断嵌套的问题,一旦流控层或驱动程序主动通过local_irq_enable打开了IRQ,而此时该中断还没处理完成,新的irq请求到达,这时代码会再次进入irq_enter,在本次嵌套中断返回时,内核不希望进行抢占调度,而是要等到最外层的中断处理完成后才做出调度动作,所以才有了禁止抢占这一处理

再调用 generic_handle_irq

最后调用 irq_exit 删除进入硬件中断的标记

__handle_domain_irq -> generic_handle_irq

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(desc);
    return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);

首先在函数 irq_to_desc 中根据发生中断的中断号,去取出它的 irq_desc 中断描述结构,然后调用 generic_handle_irq_desc:

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
    desc->handle_irq(desc);
}

这里调用了 handle_irq 函数。

所以,在上述流程中,还需要分析 irq_to_desc 流程:

struct irq_desc *irq_to_desc(unsigned int irq)
{
    return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
EXPORT_SYMBOL(irq_to_desc);

NR_IRQS 是支持的总的中断个数,当然,irq 不能够大于这个数目。所以返回 irq_desc + irq。
irq_desc 是一个全局的数组:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq = handle_bad_irq,
        .depth      = 1,
        .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

这里是这个数组的初始化的地方。所有的 handle_irq 函数都被初始化成为了 handle_bad_irq。

细心的观众可能发现了,调用这个 desc->handle_irq(desc) 函数,并不是咱们注册进去的中断处理函数啊,因为两个函数的原型定义都不一样。这个 handle_irq 是 irq_flow_handler_t 类型,而我们注册进去的服务程序是 irq_handler_t,这两个明显不是同一个东西,所以这里我们还需要继续分析。

6.1 中断相关的数据结构

Linux中,与中断相关的数据结构有3个

结构名称 作用
irq_desc IRQ 的软件层面上的资源描述,用于描述IRQ线的属性与状态,被称为中断描述符。
irqaction IRQ 的通用操作
irq_chip 对应每个芯片的具体实现,用于描述不同类型的中断控制器。

irq_chip 是一串和芯片相关的函数指针,这里定义的非常的全面,基本上和 IRQ 相关的可能出现的操作都全部定义进去了,具体根据不同的芯片,需要在不同的芯片的地方去初始化这个结构,然后这个结构会嵌入到通用的 IRQ 处理软件中去使用,使得软件处理逻辑和芯片逻辑完全的分开。

6.2 初始化Chip相关的IRQ

众所周知,启动的时候,C 语言从 start_kernel 开始,在这里面,调用了和 machine 相关的 IRQ 的初始化 init_IRQ():

asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

.....

    early_irq_init();
    init_IRQ();

.....

}

在 init_IRQ 中,调用了 machine_desc->init_irq():

void __init init_IRQ(void)
{
    int ret;

    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
        irqchip_init();
    else
        machine_desc->init_irq();

    if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
        (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
        if (!outer_cache.write_sec)
            outer_cache.write_sec = machine_desc->l2c_write_sec;
        ret = l2x0_of_init(machine_desc->l2c_aux_val,
                   machine_desc->l2c_aux_mask);
        if (ret && ret != -ENODEV)
            pr_err("L2C: failed to init: %d\n", ret);
    }

    uniphier_cache_init();
}


machine_desc->init_irq() 完成对中断控制器的初始化,为每个irq_desc结构安装合适的流控handler,为每个irq_desc结构安装irq_chip指针,使他指向正确的中断控制器所对应的irq_chip结构的实例,同时,如果该平台中的中断线有多路复用(多个中断公用一个irq中断线)的情况,还应该初始化irq_desc中相应的字段和标志,以便实现中断控制器的级联。

这里初始化的时候回调用到具体的芯片相关的中断初始化的地方。

int __init s5p_init_irq_eint(void)
{
    int irq;

    for (irq = IRQ_EINT(0); irq <= IRQ_EINT(15); irq++)
        irq_set_chip(irq, &s5p_irq_vic_eint);

    for (irq = IRQ_EINT(16); irq <= IRQ_EINT(31); irq++) {
        irq_set_chip_and_handler(irq, &s5p_irq_eint, handle_level_irq);
        set_irq_flags(irq, IRQF_VALID);
    }

    irq_set_chained_handler(IRQ_EINT16_31, s5p_irq_demux_eint16_31);
    return 0;
}

而在这些里面,都回去调用类似于:

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                  irq_flow_handler_t handle, const char *name);

irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __irq_set_handler(irq, handle, 0, NULL);
}

static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __irq_set_handler(irq, handle, 1, NULL);
}

void
irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handle,
                 void *data);

这些函数定义在 include/linux/irq.h 文件。是对芯片初始化的时候可见的 APIs,用于指定中断“流控”中的 :
irq_flow_handler_t handle

也就是中断来的时候,最后那个函数调用。
中断流控函数,分几种,电平触发的中断,边沿触发的等:

/*
 * Built-in IRQ handlers for various IRQ types,
 * callable via desc->handle_irq()
 */
extern void handle_level_irq(struct irq_desc *desc);
extern void handle_fasteoi_irq(struct irq_desc *desc);
extern void handle_edge_irq(struct irq_desc *desc);
extern void handle_edge_eoi_irq(struct irq_desc *desc);
extern void handle_simple_irq(struct irq_desc *desc);
extern void handle_untracked_irq(struct irq_desc *desc);
extern void handle_percpu_irq(struct irq_desc *desc);
extern void handle_percpu_devid_irq(struct irq_desc *desc);
extern void handle_bad_irq(struct irq_desc *desc);
extern void handle_nested_irq(unsigned int irq);

而在这些处理函数里,会去调用到 : handle_irq_event
比如:

/**
 *  handle_level_irq - Level type irq handler
 *  @desc:  the interrupt description structure for this irq
 *
 *  Level type interrupts are active as long as the hardware line has
 *  the active level. This may require to mask the interrupt and unmask
 *  it after the associated handler has acknowledged the device, so the
 *  interrupt line is back to inactive.
 */
void handle_level_irq(struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
    mask_ack_irq(desc);

    if (!irq_may_run(desc))
        goto out_unlock;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

    /*
     * If its disabled or no action available
     * keep it masked and get out of here
     */
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
        desc->istate |= IRQS_PENDING;
        goto out_unlock;
    }

    kstat_incr_irqs_this_cpu(desc);
    handle_irq_event(desc);

    cond_unmask_irq(desc);

out_unlock:
    raw_spin_unlock(&desc->lock);
}

而这个 handle_irq_event 则是调用了处理,handle_irq_event_percpu:

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

handle_irq_eventpercpu->\_handle_irq_event_percpu-> [action->handler()]
这里终于看到了调用 的地方了,就是咱们通过 request_irq 注册进去的函数

7. /proc/interrupts

这个 proc 下放置了对应中断号的中断次数和对应的 dev-name

参考:
Linux 中断之中断处理浅析


线程与对称处理器

0 条评论 学习总结 Linux Linux 线程 adtxl

1. 进程和线程

进程的概念有两个特点,一是资源所有权。一个进程包括一个存放进程映像的虚拟地址空间;二是调度/执行。一个进程沿着通过一个或多个程序的一条执行路径(轨迹)执行。
这两个特点是独立的,为了区分这两个特点,分派的单位通常称作线程,而拥有资源所有权的单位称为进程。

1.1 多线程

在多线程环境中,进程被定义成资源分配的单位和一个被保护的单位,与进程相关联的有:

  • 存放进程映像的虚拟地址空间
  • 受保护地对处理器、其他进程(用于进程间通信)、文件和I/O资源的访问。

在一个进程中,可能有一个或多个线程,每个线程有:

  • 线程执行状态(运行、就绪等)
  • 在未运行时保存的线程上下文;从某种意义上看,线程可以被看做进程内的一个被独立地操作的程序计数器
  • 一个执行栈
  • 用于每个线程局部变量的静态存储空间
  • 与进程内的其他线程共享的对进程的内存和资源的访问。

线程的优点:

  • 在一个已有进程中创建一个新线程比创建一个全新进程所需的时间要少许多。研究表明,在UNIX中,线程的创建比进程快10倍
  • 终止一个线程比终止一个进程花费的时间少
  • 同一个进程内线程间切换比进程间切换花费的时间少
  • 线程提高了不同的执行程序间通信的效率。在大多数操作系统中,独立进程间的通信需要内核的介入,以提供保护和通信所需要的机制。但是,由于同一个进程中的线程共享内存和文件,它们无需调用内核就可以互相通信。