fabric v0.6 startup

本文解析Hyperledger Fabric的CLI命令结构,介绍如何通过cobra库构建命令树,并深入探讨启动、状态查询及停止节点等功能的具体实现。

fabric/peer/main.go

main.go包含全局变量mainCmd, 使用cobra.Command赋值,详细流程暂不考虑.

// The main command describes the service and defaults to printing the help message.
var mainCmd = &cobra.Command{
    Use: "peer",
    PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
        peerCommand := getPeerCommandFromCobraCommand(cmd)
        flogging.LoggingInit(peerCommand)

        return core.CacheConfiguration()
    },
    Run: func(cmd *cobra.Command, args []string) {
        if versionFlag {
            version.Print()
        } else {
            cmd.HelpFunc()(cmd, args)
        }
    },
}

mainCmd的使用主要在func main()中,主要涉及两种操作:mainCmd.AddCommand()mainCmd.Execute(),如下:

func main() {
    ...
    mainCmd.AddCommand(version.Cmd())
    mainCmd.AddCommand(node.Cmd())
    mainCmd.AddCommand(network.Cmd())
    mainCmd.AddCommand(chaincode.Cmd())

    ...
    if mainCmd.Execute() != nil {
        os.Exit(1)
    }
    logger.Info("Exiting.....")
}

mainCmdversionnodenetworkchaincode相关的命令作为自己的子命令添加到命令树中,在main()函数的最后通过调用mainCmd.Execute(),进一步调用mainCmd.ExecuteC(),递归执行mainCmd的所有子命令,具体的函数定义在github.com/spf13/cobra/command.go中。

version.Cmd()

peer/version/version.go中定义了func Cmd(). 如下:

// Cmd returns the Cobra Command for Version
func Cmd() *cobra.Command {
    return cobraCommand
}

version.Cmd()比较简单,对比其他三个(node、network、chaincode),该函数只是返回一个构造好的cobraCommand,唯一不同的是,该cobraCommand中的Run成员变量自定义为print()函数,用于输出当前的可执行版本号。

node.Cmd()

peer/node/node.go中定义了func Cmd(). 如下:

func Cmd() *cobra.Command {
    nodeCmd.AddCommand(startCmd())
    nodeCmd.AddCommand(statusCmd())
    nodeCmd.AddCommand(stopCmd())

    return nodeCmd
}

node.Cmd()函数中nodeCmdstartCmd()statusCmd()stopCmd()作为自己的子命令添加到命令树中,其中startCmd()和项目的启动息息相关,在此只简单提及,后续进一步讨论。

  • startCmd()

peer/node/start.go中定义了func startCmd(). 如下:

func startCmd() *cobra.Command {
    // Set the flags on the node start command.
    flags := nodeStartCmd.Flags()
    flags.BoolVarP(&chaincodeDevMode, "peer-chaincodedev", "", false,
        "Whether peer in chaincode development mode")

    return nodeStartCmd
}

函数返回值为全局变量,在同一文件定义如下:

var nodeStartCmd = &cobra.Command{
    Use:   "start",
    Short: "Starts the node.",
    Long:  `Starts a node that interacts with the network.`,
    RunE: func(cmd *cobra.Command, args []string) error {
        return serve(args)
    },
}

其中成员变量RunE为自定义函数,调用同一文件下的func serve()函数,至此,fabric开始初始化诸如enginepeerpeerServer等实体。

  • statusCmd()

peer/node/status.go中定义了func statusCmd().

函数返回值同样为同一文件下的nodeStatusCmd变量,其成员变量Run调用同一文件下的status()函数,打印serverClient的状态信息。

函数的调用模式同startCmd()

  • stopCmd()

peer/node/stop.go中定义了func stopCmd().

函数返回值同样为同一文件下的nodeStopCmd变量,其成员变量Run调用同一文件下的stop()函数,通过gRPC关闭serverClientdatabase

函数的调用模式同startCmd()

network.Cmd()

peer/network/network.go中定义了func Cmd(). 如下:

func Cmd() *cobra.Command {
    networkCmd.AddCommand(loginCmd())
    networkCmd.AddCommand(listCmd())

    return networkCmd
}
  • loginCmd()
    peer/network/login.go中定义了func loginCmd()
    函数返回值同样为同一文件下的networkLoginCmd变量,其成员变量RunE(和之前的Run相同,只是返回值多了一个error类型)调用同一文件下的login()函数,该函数和PKI相关,官方注释如下:

    login() confirms the enrollmentID and secret password of the client with the CA
    and stores the enrollment certificate and key in the Devops server.

  • listCmd()
    peer/network/list.go中定义了func listCmd()
    函数返回值同样为同一文件下的networkListCmd变量,其成员变量RunE调用同一文件下的networkList()函数,该函数官方注释如下:

    Show a list of all existing network connections for the target peer node,
    includes both validating and non-validating peers

chaincode.Cmd()

peer/chaincode/chaincode.go中定义了func Cmd(). 如下:

func Cmd() *cobra.Command {
    ...
    chaincodeCmd.AddCommand(deployCmd())
    chaincodeCmd.AddCommand(invokeCmd())
    chaincodeCmd.AddCommand(queryCmd())

    return chaincodeCmd
}
  • deployCmd()
    peer/chaincode/deploy.go中定义了func deployCmd().
    函数返回值同样为同一文件下的chaincodeDeployCmd变量,其成员变量RunE调用同一文件下的chaincodeDeploy()函数,用于部署chaincode,如下:
func chaincodeDeploy(cmd *cobra.Command, args []string) error {
    spec, err := getChaincodeSpecification(cmd)
    ...
    devopsClient, err := common.GetDevopsClient(cmd)
    ...
    chaincodeDeploymentSpec, err := devopsClient.Deploy(context.Background(), spec)
    ...
    logger.Infof("Deploy result: %s", chaincodeDeploymentSpec.ChaincodeSpec)
    fmt.Printf("Deploy chaincode: %s\n", chaincodeDeploymentSpec.ChaincodeSpec.ChaincodeID.Name)

    return nil
}
  • invokeCmd()
    peer/chaincode/invoke.go中定义了func invokeCmd().
    函数返回值同样为同一文件下的chaincodeInvokeCmd变量,其成员变量RunE调用同一文件下的chaincodeInvoke()函数,该函数又进一步调用chaincodeInvokeOrQuery()chaincodeInvokeOrQuery()定义在peer/chaincode/common.go中,官方注释如下:

    chaincodeInvokeOrQuery invokes or queries the chaincode.
    If successful, the INVOKE form prints the transaction ID on STDOUT,
    and the QUERY form prints the query result on STDOUT.

  • queryCmd()
    peer/chaincode/query.go中定义了func queryCmd().
    函数返回值同样为同一文件下的chaincodeQueryCmd变量,其成员变量RunE调用同一文件下的chaincodeQuery()函数,该函数又进一步调用chaincodeInvokeOrQuery(),即 queryCmd()invokeCmd()最终调用同一个函数,通过chaincodeInvokeOrQuery()函数的第三个bool类型的参数区分指令。

补充

versionnodenetworkchaincode各自的Cmd()函数都会返回 XXXCmd(其中XXX代表networknode等), XXXCmd也是通过cobra.Command实现的,只不过versionCmd比较简单,在cobraCommand中添加一个print()函数就实现了其功能,而另外三个仍然需要继续为自己的命令树添加子命令,各命令初始化赋值通用格式如下:

var XXXCmd = &cobra.Command{
    Use:   XXXFuncName,
    Short: fmt.Sprintf("%s specific commands.", XXXFuncName),
    Long:  fmt.Sprintf("%s specific commands.", XXXFuncName),
}

其他命令的初始化也大同小异,只是单纯的成员变量的增删。

Summary

func main()中, 通过mainCmd.AddCommand(XXX.Cmd()) 逐步按功能添加各自的子命令,形成一个命令树;后期通过调用mainCmd.Execute() 递归执行各子命令,比较重要的就是,node.Cmd()最终会调用peer/node/start.go 文件下的func serve()函数,fabric开始初始化诸如enginepeerpeerServer等实体。详细过程后期会进一步分析。

命令树如下:
这里写图片描述

附录

Cobra

Cobra既是创建强大的现代CLI(Command Line Applications )应用程序的库,也是用于生成应用程序和命令文件的程序。
* Github源码:https://github.com/spf13/cobra
* Cobra简介:下载源码后github.com/spf13/viper 目录下的Readme.md文件
* ubuntu16.04下配置指令: $ go get github.com/spf13/cobra
* 具体使用:http://studygolang.com/articles/7588

viper

开源配置解决方案,满足不同的对配置文件的使用的要求,fabric采用viper来解决配置问题
* Github源码:https://github.com/spf13/viper
* Viper简介:下载源码后github.com/spf13/viper 目录下的Readme.md文件
* ubuntu16.04下配置指令: $ go get github.com/spf13/viper
* 具体使用:http://studygolang.com/articles/4244

### RISC-V 架构下 `startup.s` 文件的用法 在嵌入式开发中,`startup.s` 是一个重要的启动文件,通常用于初始化硬件环境并跳转到 C 程序入口点 `_start` 或 `main()` 函数。对于 RISC-V 架构而言,该文件的具体实现可能因目标平台的不同而有所变化,但其核心功能保持一致。 以下是关于 RISC-V 下 `startup.s` 文件的主要用途及其示例: #### 1. 初始化栈指针 (Stack Pointer) RISC-V 处理器需要设置初始栈指针以便后续程序能够正常运行。这通常是通过加载预定义的地址完成的。 ```assembly .section .text.startup .globl _start _start: li sp, STACK_TOP /* 设置栈顶地址 */ ``` 这里假设 `STACK_TOP` 已经被链接脚本定义为某个具体的数值[^1]。 #### 2. 跳转至 C 程序入口 一旦栈指针配置完毕,下一步就是调用 C 编写的主函数或者操作系统初始化代码。 ```assembly jal ra, main /* 调用 main() 并保存返回地址 */ exit: wfi /* 如果支持 WFI 指令则进入等待状态 */ j exit /* 循环防止意外执行垃圾数据 */ ``` 此部分逻辑确保了控制流从汇编层顺利过渡到了高级语言层面[^3]。 #### 3. 数据段拷贝与 BSS 清零 如果存在 `.data` 和 `.bss` 部分,则还需要额外处理这些区域的内容复制和清零操作。 ```assembly la t0, __data_load_addr__ la t1, __data_start__ la t2, __data_end__ copy_data_loop: beq t1, t2, copy_done lw t3, 0(t0) /* 加载源位置的数据 */ sw t3, 0(t1) /* 存储到目标位置 */ addi t0, t0, 4 /* 移动源索引 */ addi t1, t1, 4 /* 移动目的索引 */ j copy_data_loop copy_done: la t0, __bss_start__ la t1, __bss_end__ zero_bss_loop: beq t0, t1, zero_done sd zero_reg, 0(t0)/* 使用寄存器 'zero' 来填充字节 */ addi t0, t0, 8 /* 更新当前偏移量 */ j zero_bss_loop zero_done: ``` 以上片段展示了如何利用循环机制分别对待静态变量区进行必要的准备工作[^2]。 #### 注意事项 - 上述例子中的标签名 (`__data_*`, `__bss_*`) 可能依据实际使用的工具链有所不同; - 实际项目里可能会涉及更多细节比如中断向量表安装等步骤视需求而定。 ### 示例总结 综上所述,在构建基于 RISC-V 的应用程序时,编写合适的 `startup.s` 对于整个系统的稳定性和功能性至关重要。它不仅负责基本的上下文切换还承担着连接低级裸机世界同高层应用桥梁的角色。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值