我的站点

一个系统软件工程师的随手涂鸦

Month: 五月 2016 (Page 1 of 2)

2016年5月简讯

工作方面:
这个月工作方面的事不是很多,总结一下就是把SAP HANAsanity test跑完了。

业余时间:
(1)把Golang 101 Hacks项目从blog上移除了,选择开源到github上并同时同步到gitbook
(2)深入地学习了Delve这个Go语言的调试器,也贡献了几个patch
(3)抽出一点时间看了一下Linux kernelsed,不想把这块知识扔掉。

 

Sed基础知识

以下内容摘自sed-awk-101-hacks-ebook
Sed语法:

sed [options] {sed-commands} {input-file}
或
sed [options] -f {sed-commands-in-a-file} {input-file}

Sed命令执行流程: 读取一行到一个临时缓存区,然后对这个缓存区执行相应的命令,并输出缓存区的内容,接下来清空缓存区,读取下一行。

Capture

Sed不会修改原始文件,并且总是输出到stdout,因此通常要使用-n选项来禁止自动输出默认的pattern space。举例如下:

# cat employee.txt
101,John Doe,CEO
102,Jason Smith,IT Manager
103,Raj Reddy,Sysadmin
104,Anand Ram,Developer
105,Jane Miller,Sales Manager
# sed '2 p' employee.txt
101,John Doe,CEO
102,Jason Smith,IT Manager
102,Jason Smith,IT Manager
103,Raj Reddy,Sysadmin
104,Anand Ram,Developer
105,Jane Miller,Sales Manager
# sed -n '2 p' employee.txt
102,Jason Smith,IT Manager

如果想保存sed命令的输出,可以把这些输出重定向到某个文件:>filename

 

Linux kernel 笔记 (62)——list_head

双向链表是Linux kernel中常用的数据结构,定义如下:

struct list_head {
    struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}
...

下图选自plka

Capture

从上图可以看出,定义链表需要一个头结点,通过头结点继而可以完成插入,删除元素等操作。来看一个例子(list.c):

struct list_head {
        struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
        struct list_head name = LIST_HEAD_INIT(name)


int main(void) {
        LIST_HEAD(dev_list);
        return 0;
}

检查gcc预处理的输出:

# gcc -E -P list.c
struct list_head {
 struct list_head *next, *prev;
};
int main(void) {
 struct list_head dev_list = { &(dev_list), &(dev_list) };
 return 0;
}

可以看到,头结点dev_listprevnext都指向了自己。下面代码达到同样的效果:

struct list_head {
    struct list_head *next, *prev;
};

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

int main(void) {
    struct list_head dev_list;
    INIT_LIST_HEAD(&dev_list);
    return 0;
}

 

Delve代码分析笔记(5)——debug命令

dlv debug命令会编译并且调试一个package,其代码如下:

func debugCmd(cmd *cobra.Command, args []string) {
    status := func() int {
        var pkg string
        dlvArgs, targetArgs := splitArgs(cmd, args)

        if len(dlvArgs) > 0 {
            pkg = args[0]
        }
        err := gobuild(debugname, pkg)
        if err != nil {
            fmt.Fprintf(os.Stderr, "%v\n", err)
            return 1
        }
        fp, err := filepath.Abs("./" + debugname)
        if err != nil {
            fmt.Fprintf(os.Stderr, "%v\n", err)
            return 1
        }
        defer os.Remove(fp)

        processArgs := append([]string{"./" + debugname}, targetArgs...)
        return execute(0, processArgs, conf)
    }()
    os.Exit(status)
}

其中gobuild函数实现如下:

func gobuild(debugname, pkg string) error {
    args := []string{"-gcflags", "-N -l", "-o", debugname}
    if BuildFlags != "" {
        args = append(args, BuildFlags)
    }
    args = append(args, pkg)
    return gocommand("build", args...)
}

dlv debug命令其实就是在当前目录下临时编译生成一个没有代码优化的可执行文件,文件名是debug。接下来就是调用execute函数对这个debug文件进行调试。dlv程序退出后,会删除这个文件:defer os.Remove(fp)

 

Delve代码分析笔记(4)——构建command tree

Delve使用cobra来构造command tree。先看root command,也就是dlv

func New() *cobra.Command {
    ......
    // Main dlv root command.
    RootCommand = &cobra.Command{
        Use:   "dlv",
        Short: "Delve is a debugger for the Go programming language.",
        Long:  dlvCommandLongDesc,
    }

    RootCommand.PersistentFlags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
    RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
    RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
    RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate")
    RootCommand.PersistentFlags().IntVar(&ApiVersion, "api-version", 1, "Selects API version when headless")
    RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.")
    RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.")
    ......
}

因为dlv command没有实现run函数,所以单独运行dlv命令只会打印cobra帮忙生成的默认输出:

# dlv
Delve is a source level debugger for Go programs.

......

Usage:
  dlv [command]

Available Commands:
  version     Prints version.
  ......
Flags:
      --accept-multiclient[=false]: Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate
    ......

依次为Long descriptionUsageAvailable Commands等等。

再以trace subcommand为例,看如何把subcommand加到root command里:

......
// 'trace' subcommand.
traceCommand := &cobra.Command{
    Use:   "trace [package] regexp",
    Short: "Compile and begin tracing program.",
    Long:  "Trace program execution. Will set a tracepoint on every function matching the provided regular expression and output information when tracepoint is hit.",
    Run:   traceCmd,
}
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
RootCommand.AddCommand(traceCommand)
......

Cobra提供两种flags
a)Persistent Flags:对当前命令及其子命令都有效;
b)Local Flags:只对当前命令有效。

 

Delve代码分析笔记(3)——config

Delve程序运行起来以后,首先就会加载和解析配置文件:

func New() *cobra.Command {
    // Config setup and load.
    conf = config.LoadConfig()
    ......
}

Delve的配置文件位于用户home目录下的.dlv文件夹下,文件名是config.yml。例如,如果是root用户,则配置文件的全路径是:/root/.dlv/config.yml。目前配置文件只支持为命令指定别名。

config包只包含一个config.go文件。代码也比较简单,归纳起来就是:如果用户没有创建配置文件,则替用户创建一个(里面没有实质内容),然后读取配置文件内容,并把Config结构体(定义如下)返回给调用者。

// Config defines all configuration options available to be set through the config file.
type Config struct {
    Aliases map[string][]string
}

 

Delve代码分析笔记(2)——version

Version包只包含了一个version.go

package version

import "fmt"

// Version represents the current version of Delve.
type Version struct {
    Major    string
    Minor    string
    Patch    string
    Metadata string
    Build    string
}

var (
    // DelveVersion is the current version of Delve.
    DelveVersion = Version{Major: "0", Minor: "11", Patch: "0", Metadata: "alpha"}
)

func (v Version) String() string {
    return fmt.Sprintf("Version: %s.%s.%s-%s\nBuild: %s", v.Major, v.Minor, v.Patch, v.Metadata, v.Build)
}

Version包定义了一个Version类型的变量:DelveVersion。而Version类型的String()方法就是用来构造执行dlv version命令时,输出的字符串(cmd/dlv/cmds/commands.go):

......
// 'version' subcommand.
    versionCommand := &cobra.Command{
        Use:   "version",
        Short: "Prints version.",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Delve Debugger\n%s\n", version.DelveVersion)
        },
    }
......

执行dlv version命令:

# dlv version
Delve Debugger
Version: 0.11.0-alpha
Build:

 

Delve代码分析笔记(1)——main.go

delve的项目主页:https://github.com/derekparker/delve
main.go的代码比较简单:

package main

import (
    "github.com/derekparker/delve/cmd/dlv/cmds"
    "github.com/derekparker/delve/version"
)

// Build is the git sha of this binaries build.
var Build string

func main() {
    version.DelveVersion.Build = Build
    cmds.New().Execute()
}

main函数就干了两件事:
(1)把Build的值赋给version.DelveVersion变量中的Build成员;
(2)cmds.New()返回一个cobra.Commandtree,然后调用Execute()函数执行相应的子命令,例如dlv version

 

docker笔记(10)—— “EXPOSE”,“–expose=[]”,“-P”和“-p=[]”

A Brief Primer on Docker Networking Rules: EXPOSE, -p, -P, –link对“EXPOSE”,“--expose=[]”,“-P”和“-p=[]”做了详细介绍:

 

Capture

EXPOSE”和“--expose=[]”会expose container中的端口,但是不会映射到host上的端口,因此这些端口只能在这个host上访问。“-P”把所有expose出来的端口映射到host上的端口,而“-p=[]”则会动态指定containerhost之间的端口映射。

其它参考资料:
Why does a Docker container running a server expose port to the outside world even though said port is blocked by iptables?
Exposing a container port on host

Linux kernel 笔记 (61)——PID是0,1,2的进程

Linux kernel中,0号进程是scheduler1号进程是init/systemd(所有user thread的祖先),2号进程是[kthreadd](所有kernel thread的父进程)。

参考资料:
Which process has PID 0?
init process: ancestor of all processes?.

 

Page 1 of 2

Powered by WordPress & Theme by Anders Norén