分类 技术 下的文章

Getting Started With GitHub

GitHub 是一个在线平台,旨在促进在一个共同项目上工作的个人之间的代码托管、版本控制和协作。通过该平台,无论何时何地,都可以对项目进行操作(托管和审查代码,管理项目和与世界各地的其他开发者共同开发软件)。GitHub 平台为开源项目和私人项目都提供了项目处理功能。

关于团队项目处理的功能包括:GitHub Flow> 和 GitHub Pages 。这些功能可以让需要定期部署的团队轻松处理工作流程。另一方面,GitHub 页提供了页面用于展示开源项目、展示简历、托管博客等。

GitHub 也为个人项目提供了必要的工具,使得个人项目可以轻松地处理。它也使得个人可以更轻松地与世界分享他们的项目。

注册 GitHub 并启动一个项目

在 GitHub 上启动新项目时,您必须先使用您的电子邮件地址创建一个帐户。

github homepage

然后,在验证邮箱的时候,用户将自动登录到他们的 GitHub 帐户。

1、 创建仓库

之后,我们会被带到一个用于创建 仓库 repository 的页面。​仓库存储着包括修订历史记录在内的所有项目文件。仓库可以是公开的或者是私有的。公开的仓库可以被任何人查看,但是,只有项目所有者授予权限的人才可以提交修改到这个仓库。另一方面,私有仓库提供了额外的控制,可以将项目设置为对谁可见。因此,公开仓库适用于开源软件项目,而私有仓库主要适用于私有或闭源项目。

  • 填写 “ 仓库名称 Repository Name ” 和 “ 简短描述 Short Description ”。
  • 选中 “ 以一个 README 文件初始化 Initialize this repository with a README ”。
  • 最后,点击底部的 “ 创建仓库 Create Repository ” 按钮。

create a github repository

2、 添加分支

在 GitHub 中, 分支 branch 是一种同时操作单个仓库的各种版本的方式。默认情况下,任何创建的单个仓库都会被分配一个名为 “MASTER” 的分支,它被认为是最后一个分支。在 GitHub 中,分支在被合并到 主干 master (最后的分支)之前,可以在对仓库进行实验和编辑中发挥作用。

为了使项目适合每一个人的需求,通常情况下,总是需要添加几个格外的分支来匹配不同的项目。在主分支上创建一个分支和复制主分支时的当前状态是一样的。

add a branch to github repository

创建分支与在不同版本中保存单个文件是类似的。它通过在特定仓库上执行的任务重命名来实现。

分支在保持错误修复和功能添加工作中同样被证明是有效。在进行必要的修改后,这些分支会被合并到主分支中。

在创建仓库后创建一个分支:

  • 在这个例子中,点击仓库名称 “Hello-World” 跳转到你的新仓库。
  • 点击顶部的 “Branch:Master” 按钮,会看到一个下拉菜单,菜单里有填写分支名称的空白字段。
  • 输入分支名称,在这个例子中我们输入 “readme-edits“。
  • 按下回车键或者点击蓝色的 “ 创建分支 create branch ” 框。

这样就成功创建了两个分支:master 和 readme-edits。

3、 修改项目文件并提交

此步骤提供了关于如何更改仓库并保存修改的指导。在 GitHub 上, 提交 commit 被定义为保存的修改的意思。每一次提交都与一个 提交信息 commit message 相关联,该提交信息包含了保存的修改的历史记录,以及为何进行这些更改。这使得其他贡献者可以很轻松地知道你做出的更改以及更改的原因。

要对仓库进行更改和提交更改,请执行以下步骤:

  • 点击仓库名称 “Hello-World”。
  • 点击右上角的铅笔图标查看和编辑文件。 commit changes to github repository
  • 在编辑器中,写一些东西来确定你可以进行更改。
  • 提交消息 commit message 字段中做简要的总结,以解释为什么以及如何进行更改。
  • 点击 提交更改 commit changes 按钮保存更改。

请注意,这些更改仅仅影响到 readme-edits 分支,而不影响主分支。

commit branch to master

4、 开启一个拉取请求

​拉取请求 pull request 是一个允许贡献者提出并请求某人审查和合并某些更改到他们的分支的功能。​拉取请求还显示了几个分支的差异(diffs)。更改、添加和删减通常以红色和绿色来表示。一旦提交完成就可以开启​拉取请求,即使代码还未完成。

开启一个​拉取请求:

  • 点击​ ​拉取请求 pull requests 选项卡。 github pull request
  • 点击 新建拉取请求 new pull requests 按钮。
  • 选择 readme-edits 分支与 master 分支进行比较。 compare commit changes github
  • 确定请求,并确定这是您要提交的内容。
  • 点击创建​拉取请求绿色按钮并输入一个标题。 open a pull request in github repository
  • 按下回车键。

用户可以通过尝试创建并保存拉取请求来证实这些操作。

5、 合并拉取请求

最后一步是将 readme-edits 分支和 master 分支合并到一起。如果 readme-edits 分支和 master 分支不会产生冲突,则会显示 merge pull request 合并拉取请求 的按钮。

merge the pull request github

当合并拉取时,有必要确保 评论 comment 和其他字段被正确填写。合并拉取:

  • 点击 merge pull request 合并拉取请求 的按钮。
  • 确认合并。
  • 按下紫色的删除分支按钮,删除 readme-edits 分支,因为它已经被包含在 master 分支中。(LCTT 译注:如果是合并他人提交的拉取请求,则无需也无法删除合并过来的他人的分支。)

本文提供了 GitHub 平台从注册到使用的基本操作,接下来由大家尽情探索吧。


via: http://www.linuxandubuntu.com/home/getting-started-with-github

作者:LinuxAndUbuntu 译者:firmianay 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在上一部分我们学习了关于 DWARF 的信息,以及它如何被用于读取变量和将被执行的机器码与我们的高级语言的源码联系起来。在这一部分,我们将进入实践,实现一些我们调试器后面会使用的 DWARF 原语。我们也会利用这个机会,使我们的调试器可以在命中一个断点时打印出当前的源码上下文。

系列文章索引

随着后面文章的发布,这些链接会逐渐生效。

  1. 准备环境
  2. 断点
  3. 寄存器和内存
  4. Elves 和 dwarves
  5. 源码和信号
  6. 源码级逐步执行
  7. 源码级断点
  8. 调用栈展开
  9. 读取变量
  10. 下一步

设置我们的 DWARF 解析器

正如我在这系列文章开始时备注的,我们会使用 libelfin 来处理我们的 DWARF 信息。希望你已经在第一部分设置好了这些,如果没有的话,现在做吧,确保你使用我仓库的 fbreg 分支。

一旦你构建好了 libelfin,就可以把它添加到我们的调试器。第一步是解析我们的 ELF 可执行程序并从中提取 DWARF 信息。使用 libelfin 可以轻易实现,只需要对调试器作以下更改:

class debugger {
public:
    debugger (std::string prog_name, pid_t pid)
         : m_prog_name{std::move(prog_name)}, m_pid{pid} {
        auto fd = open(m_prog_name.c_str(), O_RDONLY);

        m_elf = elf::elf{elf::create_mmap_loader(fd)};
        m_dwarf = dwarf::dwarf{dwarf::elf::create_loader(m_elf)};
    }
    //...

private:
    //...
    dwarf::dwarf m_dwarf;
    elf::elf m_elf;
};

我们使用了 open 而不是 std::ifstream,因为 elf 加载器需要传递一个 UNIX 文件描述符给 mmap,从而可以将文件映射到内存而不是每次读取一部分。

调试信息原语

下一步我们可以实现从程序计数器的值中提取行条目(line entry)以及函数 DWARF 信息条目(function DIE)的函数。我们从 get_function_from_pc 开始:

dwarf::die debugger::get_function_from_pc(uint64_t pc) {
    for (auto &cu : m_dwarf.compilation_units()) {
        if (die_pc_range(cu.root()).contains(pc)) {
            for (const auto& die : cu.root()) {
                if (die.tag == dwarf::DW_TAG::subprogram) {
                    if (die_pc_range(die).contains(pc)) {
                        return die;
                    }
                }
            }
        }
    }

    throw std::out_of_range{"Cannot find function"};
}

这里我采用了朴素的方法,迭代遍历编译单元直到找到一个包含程序计数器的,然后迭代遍历它的子节点直到我们找到相关函数(DW_TAG_subprogram)。正如我在上一篇中提到的,如果你想要的话你可以处理类似的成员函数或者内联等情况。

接下来是 get_line_entry_from_pc

dwarf::line_table::iterator debugger::get_line_entry_from_pc(uint64_t pc) {
    for (auto &cu : m_dwarf.compilation_units()) {
        if (die_pc_range(cu.root()).contains(pc)) {
            auto &lt = cu.get_line_table();
            auto it = lt.find_address(pc);
            if (it == lt.end()) {
                throw std::out_of_range{"Cannot find line entry"};
            }
            else {
                return it;
            }
        }
    }

    throw std::out_of_range{"Cannot find line entry"};
}

同样,我们可以简单地找到正确的编译单元,然后查询行表获取相关的条目。

打印源码

当我们命中一个断点或者逐步执行我们的代码时,我们会想知道处于源码中的什么位置。

void debugger::print_source(const std::string& file_name, unsigned line, unsigned n_lines_context) {
    std::ifstream file {file_name};

    //获得一个所需行附近的窗口
    auto start_line = line <= n_lines_context ? 1 : line - n_lines_context;
    auto end_line = line + n_lines_context + (line < n_lines_context ? n_lines_context - line : 0) + 1;

    char c{};
    auto current_line = 1u;
    //跳过 start_line 之前的行
    while (current_line != start_line && file.get(c)) {
        if (c == '\n') {
            ++current_line;
        }
    }

    //如果我们在当前行则输出光标
    std::cout << (current_line==line ? "> " : "  ");

    //输出行直到 end_line
    while (current_line <= end_line && file.get(c)) {
        std::cout << c;
        if (c == '\n') {
            ++current_line;
            //如果我们在当前行则输出光标
            std::cout << (current_line==line ? "> " : "  ");
        }
    }

    //输出换行确保恰当地清空了流
    std::cout << std::endl;
}

现在我们可以打印出源码了,我们需要将这些通过钩子添加到我们的调试器。实现这个的一个好地方是当调试器从一个断点或者(最终)逐步执行得到一个信号时。到了这里,我们可能想要给我们的调试器添加一些更好的信号处理。

更好的信号处理

我们希望能够得知什么信号被发送给了进程,同样我们也想知道它是如何产生的。例如,我们希望能够得知是否由于命中了一个断点从而获得一个 SIGTRAP,还是由于逐步执行完成、或者是产生了一个新线程等等导致的。幸运的是,我们可以再一次使用 ptrace。可以给 ptrace 的一个命令是 PTRACE_GETSIGINFO,它会给你被发送给进程的最后一个信号的信息。我们类似这样使用它:

siginfo_t debugger::get_signal_info() {
    siginfo_t info;
    ptrace(PTRACE_GETSIGINFO, m_pid, nullptr, &info);
    return info;
}

这会给我们一个 siginfo_t 对象,它能提供以下信息:

siginfo_t {
    int      si_signo;     /* 信号编号 */
    int      si_errno;     /* errno 值 */
    int      si_code;      /* 信号代码 */
    int      si_trapno;    /* 导致生成硬件信号的陷阱编号
                              (大部分架构中都没有使用) */
    pid_t    si_pid;       /* 发送信号的进程 ID */
    uid_t    si_uid;       /* 发送信号进程的用户 ID */
    int      si_status;    /* 退出值或信号 */
    clock_t  si_utime;     /* 消耗的用户时间 */
    clock_t  si_stime;     /* 消耗的系统时间 */
    sigval_t si_value;     /* 信号值 */
    int      si_int;       /* POSIX.1b 信号 */
    void    *si_ptr;       /* POSIX.1b 信号 */
    int      si_overrun;   /* 计时器 overrun 计数;
                              POSIX.1b 计时器 */
    int      si_timerid;   /* 计时器 ID; POSIX.1b 计时器 */
    void    *si_addr;      /* 导致错误的内存地址 */
    long     si_band;      /* Band event (在 glibc 2.3.2 和之前版本中是 int 类型) */
    int      si_fd;        /* 文件描述符 */
    short    si_addr_lsb;  /* 地址的最不重要位
                              (自 Linux 2.6.32) */
    void    *si_lower;     /* 出现地址违规的下限 (自 Linux 3.19) */
    void    *si_upper;     /* 出现地址违规的上限 (自 Linux 3.19) */
    int      si_pkey;      /* PTE 上导致错误的保护键 (自 Linux 4.6) */
    void    *si_call_addr; /* 系统调用指令的地址
                              (自 Linux 3.5) */
    int      si_syscall;   /* 系统调用尝试次数
                              (自 Linux 3.5) */
    unsigned int si_arch;  /* 尝试系统调用的架构
                              (自 Linux 3.5) */
}

我只需要使用 si_signo 就可以找到被发送的信号,使用 si_code 来获取更多关于信号的信息。放置这些代码的最好位置是我们的 wait_for_signal 函数:

void debugger::wait_for_signal() {
    int wait_status;
    auto options = 0;
    waitpid(m_pid, &wait_status, options);

    auto siginfo = get_signal_info();

    switch (siginfo.si_signo) {
    case SIGTRAP:
        handle_sigtrap(siginfo);
        break;
    case SIGSEGV:
        std::cout << "Yay, segfault. Reason: " << siginfo.si_code << std::endl;
        break;
    default:
        std::cout << "Got signal " << strsignal(siginfo.si_signo) << std::endl;
    }
}

现在再来处理 SIGTRAP。知道当命中一个断点时会发送 SI_KERNELTRAP_BRKPT,而逐步执行结束时会发送 TRAP_TRACE 就足够了:

void debugger::handle_sigtrap(siginfo_t info) {
    switch (info.si_code) {
    //如果命中了一个断点其中的一个会被设置
    case SI_KERNEL:
    case TRAP_BRKPT:
    {
        set_pc(get_pc()-1); //将程序计数器的值设置为它应该指向的地方
        std::cout << "Hit breakpoint at address 0x" << std::hex << get_pc() << std::endl;
        auto line_entry = get_line_entry_from_pc(get_pc());
        print_source(line_entry->file->path, line_entry->line);
        return;
    }
    //如果信号是由逐步执行发送的,这会被设置
    case TRAP_TRACE:
        return;
    default:
        std::cout << "Unknown SIGTRAP code " << info.si_code << std::endl;
        return;
    }
}

这里有一大堆不同风格的信号你可以处理。查看 man sigaction 获取更多信息。

由于当我们收到 SIGTRAP 信号时我们已经修正了程序计数器的值,我们可以从 step_over_breakpoint 中移除这些代码,现在它看起来类似:

void debugger::step_over_breakpoint() {
    if (m_breakpoints.count(get_pc())) {
        auto& bp = m_breakpoints[get_pc()];
        if (bp.is_enabled()) {
            bp.disable();
            ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr);
            wait_for_signal();
            bp.enable();
        }
    }
}

测试

现在你应该可以在某个地址设置断点,运行程序然后看到打印出了源码,而且正在被执行的行被光标标记了出来。

后面我们会添加设置源码级别断点的功能。同时,你可以从这里获取该博文的代码。


via: https://blog.tartanllama.xyz/c++/2017/04/24/writing-a-linux-debugger-source-signal/

作者:TartanLlama 译者:ictlyh 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

snaps

Ubuntu Core 已经正式发布(LCTT 译注:指 2016 年 11 月发布的 Ubuntu Snappy Core 16 ),也许是时候让你的 snap 包进入商店了!

交付和商店的概念

首先回顾一下我们是怎么通过商店管理 snap 包的吧。

每次你上传 snap 包,商店都会为其分配一个修订版本号,并且商店中针对特定 snap 包 的版本号都是唯一的。

但是第一次上传 snap 包的时候,我们首先要为其注册一个还没有被使用的名字,这很容易。

商店中所有的修订版本都可以释放到多个通道中,这些通道只是概念上定义的,以便给用户一个稳定或风险等级的参照,这些通道有:

  • 稳定(stable)
  • 候选(candidate)
  • 测试(beta)
  • 边缘(edge)

理想情况下,如果我们设置了 CI/CD 过程,那么每天或在每次更新源码时都会将其推送到边缘通道。在此过程中有两件事需要考虑。

首先在开始的时候,你最好制作一个不受限制的 snap 包,因为在这种新范例下,snap 包的大部分功能都能不受限制地工作。考虑到这一点,你的项目开始时 confinement 将被设置为 devmode(LCTT 译注:这是 snapcraft.yaml 中的一个键及其可选值)。这使得你在开发的早期阶段,仍然可以让你的 snap 包进入商店。一旦所有的东西都得到了 snap 包运行的安全模型的充分支持,那么就可以将 confinement 修改为 strict

好了,假设你在限制方面已经做好了,并且也开始了一个对应边缘通道的 CI/CD 过程,但是如果你也想确保在某些情况下,早期版本 master 分支新的迭代永远也不会进入稳定或候选通道,那么我们可以使用 gadge 设置。如果 snap 包的 gadge 设置为 devel (LCTT注:这是 snapcraft.yaml 中的一个键及其可选值),商店将会永远禁止你将 snap 包释放到稳定和候选通道。

在这个过程中,我们有时可能想要发布一个修订版本到测试通道,以便让有些用户更愿意去跟踪它(一个好的发布管理流程应该比一个随机的日常构建更有用)。这个阶段结束后,如果希望人们仍然能保持更新,我们可以选择关闭测试通道,从一个特定的时间点开始我们只计划发布到候选和稳定通道,通过关闭测试通道我们将使该通道跟随稳定列表中的下一个开放通道,在这里是候选通道。而如果候选通道跟随的是稳定通道后,那么最终得到是稳定通道了。

进入 Snapcraft

那么所有这些给定的概念是如何在 snapcraft 中配合使用的?首先我们需要登录:

$ snapcraft login
Enter your Ubuntu One SSO credentials.
Email: [email protected]
Password: **************
Second-factor auth: 123456

在登录之后,我们就可以开始注册 snap 了。例如,我们想要注册一个虚构的 snap 包 awesome-database:

$ snapcraft register awesome-database
We always want to ensure that users get the software they expect
for a particular name.

If needed, we will rename snaps to ensure that a particular name
reflects the software most widely expected by our community.

For example, most people would expect ‘thunderbird’ to be published by
Mozilla. They would also expect to be able to get other snaps of
Thunderbird as 'thunderbird-sergiusens'.

Would you say that MOST users will expect 'a' to come from
you, and be the software you intend to publish there? [y/N]: y

You are now the publisher for 'awesome-database'

假设我们已经构建了 snap 包,接下来我们要做的就是把它上传到商店。我们可以在同一个命令中使用快捷方式和 --release 选项:

$ snapcraft push awesome-databse_0.1_amd64.snap --release edge
Uploading awesome-database_0.1_amd64.snap [=================] 100%
Processing....
Revision 1 of 'awesome-database' created.

Channel    Version    Revision
stable     -          -
candidate  -          -
beta       -          -
edge       0.1        1

The edge channel is now open. 

如果我们试图将其发布到稳定通道,商店将会阻止我们:

$ snapcraft release awesome-database 1 stable
Revision 1 (devmode) cannot target a stable channel (stable, grade: devel) 

这样我们不会搞砸,也不会让我们的忠实用户使用它。现在,我们将最终推出一个值得发布到稳定通道的修订版本:

$ snapcraft push awesome-databse_0.1_amd64.snap
Uploading awesome-database_0.1_amd64.snap [=================] 100%
Processing....
Revision 10 of 'awesome-database' created. 

注意, 版本号 version (LCTT 译注:这里指的是 snap 包名中 0.1 这个版本号)只是一个友好的标识符,真正重要的是商店为我们生成的 修订版本号 Revision (LCTT 译注:这里生成的修订版本号为 10)。现在让我们把它释放到稳定通道:

$ snapcraft release awesome-database 10 stable
Channel    Version    Revision
stable     0.1        10
candidate  ^          ^
beta       ^          ^
edge       0.1        10

The 'stable' channel is now open. 

在这个针对我们正在使用架构最终的通道映射视图中,可以看到边缘通道将会被固定在修订版本 10 上,并且测试和候选通道将会跟随现在修订版本为 10 的稳定通道。由于某些原因,我们决定将专注于稳定性并让我们的 CI/CD 推送到测试通道。这意味着我们的边缘通道将会略微过时,为了避免这种情况,我们可以关闭这个通道:

 $ snapcraft close awesome-database edge
Arch    Channel    Version    Revision
amd64   stable     0.1        10
        candidate  ^          ^
        beta       ^          ^
        edge       ^          ^

The edge channel is now closed. 

在当前状态下,所有通道都跟随着稳定通道,因此订阅了候选、测试和边缘通道的人也将跟踪稳定通道的改动。比如就算修订版本 11 只发布到稳定通道,其他通道的人们也能看到它。

这个清单还提供了完整的体系结构视图,在本例中,我们只使用了 amd64。

获得更多的信息

有时过了一段时间,我们想知道商店中的某个 snap 包的历史记录和现在的状态是什么样的,这里有两个命令,一个是直截了当输出当前的状态,它会给我们一个熟悉的结果:

 $ snapcraft status awesome-database
Arch    Channel    Version    Revision
amd64   stable     0.1        10
        candidate  ^          ^
        beta       ^          ^
        edge       ^          ^ 

我们也可以通过下面的命令获得完整的历史记录:

 $ snapcraft history awesome-database
Rev.    Uploaded              Arch       Version    Channels
3       2016-09-30T12:46:21Z  amd64      0.1        stable*
...
...
...
2       2016-09-30T12:38:20Z  amd64      0.1        -
1       2016-09-30T12:33:55Z  amd64      0.1        - 

结束语

希望这篇文章能让你对 Snap 商店能做的事情有一个大概的了解,并让更多的人开始使用它!


via: https://insights.ubuntu.com/2016/11/15/making-your-snaps-available-to-the-store-using-snapcraft/

译者简介:

snapcraft.io 的钉子户,对 Ubuntu Core、Snaps 和 Snapcraft 有着浓厚的兴趣,并致力于将这些还在快速发展的新技术通过翻译或原创的方式介绍到中文世界。有兴趣的小伙伴也可以关注译者个人的公众号: Snapcraft,近期会在上面连载几篇有关 Core snap 发布策略、交付流程和验证流程的文章,欢迎围观 :)

作者:Sergio Schvezov 译者:Snapcrafter 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Meld 是 Linux 上功能丰富的可视化比较和合并工具。如果你是第一次接触,你可以进入我们的初学者指南,了解该程序的工作原理,如果你已经阅读过或正在使用 Meld 进行基本的比较/合并任务,你将很高兴了解本教程的东西,在本教程中,我们将讨论一些非常有用的技巧,这将让你使用工具的体验更好。

但在我们跳到安装和解释部分之前,值得一提的是,本教程中介绍的所有说明和示例已在 Ubuntu 14.04 上进行了测试,而我们使用的 Meld 版本为 3.14.2

1、 跳转

你可能已经知道(我们也在初学者指南中也提到过这一点),标准滚动不是在使用 Meld 时在更改之间跳转的唯一方法 - 你可以使用向上和向下箭头键轻松地从一个更改跳转到另一个更改位于编辑区域上方的窗格中:

Navigating in Meld

但是,这需要你将鼠标指针移动到这些箭头,然后再次单击其中一个(取决于你要去哪里 - 向上或向下)。你会很高兴知道,存在另一种更简单的方式来跳转:只需使用鼠标的滚轮即可在鼠标指针位于中央更改栏上时进行滚动。

The change bar

这样,你就可以在视线不离开或者分心的情况下进行跳转,

2、 可以对更改进行的操作

看下上一节的最后一个屏幕截图。你知道那些黑箭头做什么吧?默认情况下,它们允许你执行合并/更改操作 - 当没有冲突时进行合并,并在同一行发生冲突时进行更改。

但是你知道你可以根据需要删除个别的更改么?是的,这是可能的。为此,你需要做的是在处理更改时按下 Shift 键。你会观察到箭头被变成了十字架。

Things you can do with changes

只需点击其中任何一个,相应的更改将被删除。

不仅是删除,你还可以确保冲突的更改不会在合并时更改行。例如,以下是一个冲突变化的例子:

Delete changes in Meld

现在,如果你点击任意两个黑色箭头,箭头指向的行将被改变,并且将变得与其他文件的相应行相似。只要你想这样做,这是没问题的。但是,如果你不想要更改任何行呢?相反,目的是将更改的行在相应行的上方或下方插入到其他文件中。

我想说的是,例如,在上面的截图中,需要在 “test23” 之上或之下添加 “test 2”,而不是将 “test23” 更改为 “test2”。你会很高兴知道在 Meld 中这是可能的。就像你按下 Shift 键删除注释一样,在这种情况下,你必须按下 Ctrl 键。

你会观察到当前操作将被更改为插入 - 双箭头图标将确认这一点 。

Change actions

从箭头的方向看,此操作可帮助用户将当前更改插入到其他文件中的相应更改 (如所选择的)。

3、 自定义文件在 Meld 的编辑器区域中显示的方式

有时候,你希望 Meld 的编辑区域中的文字大小变大(为了更好或更舒适的浏览),或者你希望文本行被包含而不是脱离视觉区域(意味着你不要想使用底部的水平滚动条)。

Meld 在 Editor 选项卡(Edit->Preferences->Editor)的 Preferences 菜单中提供了一些显示和字体相关的自定义选项,你可以进行这些调整:

Meld preferences

在这里你可以看到,默认情况下,Meld 使用系统定义的字体宽度。只需取消选中 Font 类别下的框,你将有大量的字体类型和大小选项可供选择。

然后在 Display 部分,你将看到我们正在讨论的所有自定义选项:你可以设置 Tab 宽度、告诉工具是否插入空格而不是 tab、启用/禁用文本换行、使Meld显示行号和空白(在某些情况下非常有用)以及使用语法突出显示。

4、 过滤文本

有时候,并不是所有的修改都是对你很重要的。例如,在比较两个 C 编程文件时,你可能不希望 Meld 显示注释中的更改,因为你只想专注于与代码相关的更改。因此,在这种情况下,你可以告诉 Meld 过滤(或忽略)与注释相关的更改。

例如,这里是 Meld 中的一个比较,其中由工具高亮了注释相关更改:

Filter Text in Meld

而在这种情况下,Meld 忽略了相同的变化,仅关注与代码相关的变更:

Ignore Changes in Meld

很酷,不是吗?那么这是怎么回事?为此,我是在 “Edit->Preferences->Text Filters” 标签中启用了 “C comments” 文本过滤器:

C-Comments in Meld

如你所见,除了 “C comments” 之外,你还可以过滤掉 C++ 注释、脚本注释、引导或所有的空格等。此外,你还可以为你处理的任何特定情况定义自定义文本过滤器。例如,如果你正在处理日志文件,并且不希望 Meld 高亮显示特定模式开头的行中的更改,则可以为该情况定义自定义文本过滤器。

但是,请记住,要定义一个新的文本过滤器,你需要了解 Python 语言以及如何使用该语言创建正则表达式。

总结

这里讨论的所有四个技巧都不是很难理解和使用(当然,除了你想立即创建自定义文本过滤器),一旦你开始使用它们,你会认为他们是真的有好处。这里的关键是要继续练习,否则你学到的任何技巧不久后都会忘记。

你还知道或者使用其他任何中级 Meld 的贴士和技巧么?如果有的话,欢迎你在下面的评论中分享。


via: https://www.howtoforge.com/tutorial/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/

作者:Ansh 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你是否曾经对操作系统为何能够执行应用程序而感到疑惑?那么本文将为你揭开操作系统引导与启动的面纱。

理解操作系统开机引导和启动过程对于配置操作系统和解决相关启动问题是至关重要的。该文章陈述了 GRUB2 引导装载程序开机引导装载内核的过程和 systemd 初始化系统执行开机启动操作系统的过程。

事实上,操作系统的启动分为两个阶段: 引导 boot 启动 startup 。引导阶段开始于打开电源开关,结束于内核初始化完成和 systemd 进程成功运行。启动阶段接管了剩余工作,直到操作系统进入可操作状态。

总体来说,Linux 的开机引导和启动过程是相当容易理解,下文将分节对于不同步骤进行详细说明。

  • BIOS 上电自检(POST)
  • 引导装载程序 (GRUB2)
  • 内核初始化
  • 启动 systemd,其是所有进程之父。

注意,本文以 GRUB2 和 systemd 为载体讲述操作系统的开机引导和启动过程,是因为这二者是目前主流的 linux 发行版本所使用的引导装载程序和初始化软件。当然另外一些过去使用的相关软件仍然在一些 Linux 发行版本中使用。

引导过程

引导过程能以两种方式之一初始化。其一,如果系统处于关机状态,那么打开电源按钮将开启系统引导过程。其二,如果操作系统已经运行在一个本地用户(该用户可以是 root 或其他非特权用户),那么用户可以借助图形界面或命令行界面通过编程方式发起一个重启操作,从而触发系统引导过程。重启包括了一个关机和重新开始的操作。

BIOS 上电自检(POST)

上电自检过程中其实 Linux 没有什么也没做,上电自检主要由硬件的部分来完成,这对于所有操作系统都一样。当电脑接通电源,电脑开始执行 BIOS( 基本输入输出系统 Basic I/O System )的 POST( 上电自检 Power On Self Test )过程。

在 1981 年,IBM 设计的第一台个人电脑中,BIOS 被设计为用来初始化硬件组件。POST 作为 BIOS 的组成部分,用于检验电脑硬件基本功能是否正常。如果 POST 失败,那么这个电脑就不能使用,引导过程也将就此中断。

BIOS 上电自检确认硬件的基本功能正常,然后产生一个 BIOS 中断 INT 13H,该中断指向某个接入的可引导设备的引导扇区。它所找到的包含有效的引导记录的第一个引导扇区将被装载到内存中,并且控制权也将从引导扇区转移到此段代码。

引导扇区是引导加载器真正的第一阶段。大多数 Linux 发行版本使用的引导加载器有三种:GRUB、GRUB2 和 LILO。GRUB2 是最新的,也是相对于其他老的同类程序使用最广泛的。

GRUB2

GRUB2 全称是 GRand Unified BootLoader,Version 2(第二版大一统引导装载程序)。它是目前流行的大部分 Linux 发行版本的主要引导加载程序。GRUB2 是一个用于计算机寻找操作系统内核并加载其到内存的智能程序。由于 GRUB 这个单词比 GRUB2 更易于书写和阅读,在下文中,除特殊指明以外,GRUB 将代指 GRUB2。

GRUB 被设计为兼容操作系统多重引导规范,它能够用来引导不同版本的 Linux 和其他的开源操作系统;它还能链式加载专有操作系统的引导记录。

GRUB 允许用户从任何给定的 Linux 发行版本的几个不同内核中选择一个进行引导。这个特性使得操作系统,在因为关键软件不兼容或其它某些原因升级失败时,具备引导到先前版本的内核的能力。GRUB 能够通过文件 /boot/grub/grub.conf 进行配置。(LCTT 译注:此处指 GRUB1)

GRUB1 现在已经逐步被弃用,在大多数现代发行版上它已经被 GRUB2 所替换,GRUB2 是在 GRUB1 的基础上重写完成。基于 Red Hat 的发行版大约是在 Fedora 15 和 CentOS/RHEL 7 时升级到 GRUB2 的。GRUB2 提供了与 GRUB1 同样的引导功能,但是 GRUB2 也是一个类似主框架(mainframe)系统上的基于命令行的前置操作系统(Pre-OS)环境,使得在预引导阶段配置更为方便和易操作。GRUB2 通过 /boot/grub2/grub.cfg 进行配置。

两个 GRUB 的最主要作用都是将内核加载到内存并运行。两个版本的 GRUB 的基本工作方式一致,其主要阶段也保持相同,都可分为 3 个阶段。在本文将以 GRUB2 为例进行讨论其工作过程。GRUB 或 GRUB2 的配置,以及 GRUB2 的命令使用均超过本文范围,不会在文中进行介绍。

虽然 GRUB2 并未在其三个引导阶段中正式使用这些 阶段 stage 名词,但是为了讨论方便,我们在本文中使用它们。

阶段 1

如上文 POST(上电自检)阶段提到的,在 POST 阶段结束时,BIOS 将查找在接入的磁盘中查找引导记录,其通常位于 MBR( 主引导记录 Master Boot Record ),它加载它找到的第一个引导记录中到内存中,并开始执行此代码。引导代码(及阶段 1 代码)必须非常小,因为它必须连同分区表放到硬盘的第一个 512 字节的扇区中。 在传统的常规 MBR 中,引导代码实际所占用的空间大小为 446 字节。这个阶段 1 的 446 字节的文件通常被叫做引导镜像(boot.img),其中不包含设备的分区信息,分区是一般单独添加到引导记录中。

由于引导记录必须非常的小,它不可能非常智能,且不能理解文件系统结构。因此阶段 1 的唯一功能就是定位并加载阶段 1.5 的代码。为了完成此任务,阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。在加载阶段 1.5 代码进入内存后,控制权将由阶段 1 转移到阶段 1.5。

阶段 1.5

如上所述,阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。该空间由于历史上的技术原因而空闲。第一个分区的开始位置在扇区 63 和 MBR(扇区 0)之间遗留下 62 个 512 字节的扇区(共 31744 字节),该区域用于存储阶段 1.5 的代码镜像 core.img 文件。该文件大小为 25389 字节,故此区域有足够大小的空间用来存储 core.img。

因为有更大的存储空间用于阶段 1.5,且该空间足够容纳一些通用的文件系统驱动程序,如标准的 EXT 和其它的 Linux 文件系统,如 FAT 和 NTFS 等。GRUB2 的 core.img 远比更老的 GRUB1 阶段 1.5 更复杂且更强大。这意味着 GRUB2 的阶段 2 能够放在标准的 EXT 文件系统内,但是不能放在逻辑卷内。故阶段 2 的文件可以存放于 /boot 文件系统中,一般在 /boot/grub2 目录下。

注意 /boot 目录必须放在一个 GRUB 所支持的文件系统(并不是所有的文件系统均可)。阶段 1.5 的功能是开始执行存放阶段 2 文件的 /boot 文件系统的驱动程序,并加载相关的驱动程序。

阶段 2

GRUB 阶段 2 所有的文件都已存放于 /boot/grub2 目录及其几个子目录之下。该阶段没有一个类似于阶段 1 与阶段 1.5 的镜像文件。相应地,该阶段主要需要从 /boot/grub2/i386-pc 目录下加载一些内核运行时模块。

GRUB 阶段 2 的主要功能是定位和加载 Linux 内核到内存中,并转移控制权到内核。内核的相关文件位于 /boot 目录下,这些内核文件可以通过其文件名进行识别,其文件名均带有前缀 vmlinuz。你可以列出 /boot 目录中的内容来查看操作系统中当前已经安装的内核。

GRUB2 跟 GRUB1 类似,支持从 Linux 内核选择之一引导启动。Red Hat 包管理器(DNF)支持保留多个内核版本,以防最新版本内核发生问题而无法启动时,可以恢复老版本的内核。默认情况下,GRUB 提供了一个已安装内核的预引导菜单,其中包括问题诊断菜单(recuse)以及恢复菜单(如果配置已经设置恢复镜像)。

阶段 2 加载选定的内核到内存中,并转移控制权到内核代码。

内核

内核文件都是以一种自解压的压缩格式存储以节省空间,它与一个初始化的内存映像和存储设备映射表都存储于 /boot 目录之下。

在选定的内核加载到内存中并开始执行后,在其进行任何工作之前,内核文件首先必须从压缩格式解压自身。一旦内核自解压完成,则加载 systemd 进程(其是老式 System V 系统的 init 程序的替代品),并转移控制权到 systemd。

这就是引导过程的结束。此刻,Linux 内核和 systemd 处于运行状态,但是由于没有其他任何程序在执行,故其不能执行任何有关用户的功能性任务。

启动过程

启动过程紧随引导过程之后,启动过程使 Linux 系统进入可操作状态,并能够执行用户功能性任务。

systemd

systemd 是所有进程的父进程。它负责将 Linux 主机带到一个用户可操作状态(可以执行功能任务)。systemd 的一些功能远较旧式 init 程序更丰富,可以管理运行中的 Linux 主机的许多方面,包括挂载文件系统,以及开启和管理 Linux 主机的系统服务等。但是 systemd 的任何与系统启动过程无关的功能均不在此文的讨论范围。

首先,systemd 挂载在 /etc/fstab 中配置的文件系统,包括内存交换文件或分区。据此,systemd 必须能够访问位于 /etc 目录下的配置文件,包括它自己的。systemd 借助其配置文件 /etc/systemd/system/default.target 决定 Linux 系统应该启动达到哪个状态(或 目标态 target )。default.target 是一个真实的 target 文件的符号链接。对于桌面系统,其链接到 graphical.target,该文件相当于旧式 systemV init 方式的 runlevel 5。对于一个服务器操作系统来说,default.target 更多是默认链接到 multi-user.target, 相当于 systemV 系统的 runlevel 3emergency.target 相当于单用户模式。

(LCTT 译注:“target” 是 systemd 新引入的概念,目前尚未发现有官方的准确译名,考虑到其作用和使用的上下文环境,我们认为翻译为“目标态”比较贴切。以及,“unit” 是指 systemd 中服务和目标态等各个对象/文件,在此依照语境译作“单元”。)

注意,所有的 目标态 target 服务 service 均是 systemd 的 单元 unit

如下表 1 是 systemd 启动的 目标态 target 和老版 systemV init 启动 运行级别 runlevel 的对比。这个 systemd 目标态别名 是为了 systemd 向前兼容 systemV 而提供。这个目标态别名允许系统管理员(包括我自己)用 systemV 命令(例如 init 3)改变运行级别。当然,该 systemV 命令是被转发到 systemd 进行解释和执行的。

SystemV 运行级别systemd 目标态systemd 目标态别名描述
halt.target 停止系统运行但不切断电源。
0poweroff.targetrunlevel0.target停止系统运行并切断电源.
Semergency.target 单用户模式,没有服务进程运行,文件系统也没挂载。这是一个最基本的运行级别,仅在主控制台上提供一个 shell 用于用户与系统进行交互。
1rescue.targetrunlevel1.target挂载了文件系统,仅运行了最基本的服务进程的基本系统,并在主控制台启动了一个 shell 访问入口用于诊断。
2 runlevel2.target多用户,没有挂载 NFS 文件系统,但是所有的非图形界面的服务进程已经运行。
3multi-user.targetrunlevel3.target所有服务都已运行,但只支持命令行接口访问。
4 runlevel4.target未使用。
5graphical.targetrunlevel5.target多用户,且支持图形界面接口。
6reboot.targetrunlevel6.target重启。
default.target 这个 目标态 target 是总是 multi-user.targetgraphical.target 的一个符号链接的别名。systemd 总是通过 default.target 启动系统。default.target 绝不应该指向 halt.targetpoweroff.targetreboot.target

表 1 老版本 systemV 的 运行级别与 systemd 与 目标态 target 或目标态别名的比较

每个 目标态 target 有一个在其配置文件中描述的依赖集,systemd 需要首先启动其所需依赖,这些依赖服务是 Linux 主机运行在特定的功能级别所要求的服务。当配置文件中所有的依赖服务都加载并运行后,即说明系统运行于该目标级别。

systemd 也会查看老式的 systemV init 目录中是否存在相关启动文件,若存在,则 systemd 根据这些配置文件的内容启动对应的服务。在 Fedora 系统中,过时的网络服务就是通过该方式启动的一个实例。

如下图 1 是直接从 bootup 的 man 页面拷贝而来。它展示了在 systemd 启动过程中一般的事件序列和确保成功的启动的基本的顺序要求。

sysinit.targetbasic.target 目标态可以被视作启动过程中的状态检查点。尽管 systemd 的设计初衷是并行启动系统服务,但是部分服务或功能目标态是其它服务或目标态的启动的前提。系统将暂停于检查点直到其所要求的服务和目标态都满足为止。

sysinit.target 状态的到达是以其所依赖的所有资源模块都正常启动为前提的,所有其它的单元,如文件系统挂载、交换文件设置、设备管理器的启动、随机数生成器种子设置、低级别系统服务初始化、加解密服务启动(如果一个或者多个文件系统加密的话)等都必须完成,但是在 sysinit.target 中这些服务与模块是可以并行启动的。

sysinit.target 启动所有的低级别服务和系统初具功能所需的单元,这些都是进入下一阶段 basic.target 的必要前提。

图 1:systemd 的启动流程

sysinit.target 的条件满足以后,systemd 接下来启动 basic.target,启动其所要求的所有单元。 basic.target 通过启动下一目标态所需的单元而提供了更多的功能,这包括各种可执行文件的目录路径、通信 sockets,以及定时器等。

最后,用户级目标态(multi-user.targetgraphical.target) 可以初始化了,应该注意的是 multi-user.target 必须在满足图形化目标态 graphical.target 的依赖项之前先达成。

图 1 中,以 * 开头的目标态是通用的启动状态。当到达其中的某一目标态,则说明系统已经启动完成了。如果 multi-user.target 是默认的目标态,则成功启动的系统将以命令行登录界面呈现于用户。如果 graphical.target 是默认的目标态,则成功启动的系统将以图形登录界面呈现于用户,界面的具体样式将根据系统所配置的显示管理器而定。

故障讨论

最近我需要改变一台使用 GRUB2 的 Linux 电脑的默认引导内核。我发现一些 GRUB2 的命令在我的系统上不能用,也可能是我使用方法不正确。至今,我仍然不知道是何原因导致,此问题需要进一步探究。

grub2-set-default 命令没能在配置文件 /etc/default/grub 中成功地设置默认内核索引,以至于期望的替代内核并没有被引导启动。故在该配置文件中我手动更改 GRUB_DEFAULT=savedGRUB_DEFAULT=2,2 是我需要引导的安装好的内核文件的索引。然后我执行命令 grub2-mkconfig > /boot/grub2/grub.cfg 创建了新的 GRUB 配置文件,该方法如预期的规避了问题,并成功引导了替代的内核。

结论

GRUB2、systemd 初始化系统是大多数现代 Linux 发行版引导和启动的关键组件。尽管在实际中,systemd 的使用还存在一些争议,但是 GRUB2 与 systemd 可以密切地配合先加载内核,然后启动一个业务系统所需要的系统服务。

尽管 GRUB2 和 systemd 都比其前任要更加复杂,但是它们更加容易学习和管理。在 man 页面有大量关于 systemd 的帮助说明,freedesktop.org 也在线收录了完整的此帮助说明。下面有更多相关信息链接。

附加资源


作者简介:

David Both 居住在美国北卡罗纳州的首府罗利,是一个 Linux 开源贡献者。他已经从事 IT 行业 40 余年,在 IBM 教授 OS/2 20余年。1981 年,他在 IBM 开发了第一个关于最初的 IBM 个人电脑的培训课程。他也曾在 Red Hat 教授 RHCE 课程,也曾供职于 MCI worldcom,Cico 以及北卡罗纳州等。他已经为 Linux 开源社区工作近 20 年。


via: https://opensource.com/article/17/2/linux-boot-and-startup

作者:David Both 译者: penghuster 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

去年在柏林的分布式系统峰会上,Docker 的负责人 Marcos NilsJonathan Leibiusky 宣称已经开始研究浏览器内置 Docker 的方案,帮助人们学习 Docker。 几天后,Play-with-docker(PWD)就诞生了。

PWD 像是一个 Docker 游乐场,用户在几秒钟内就可以运行 Docker 命令。 还可以在浏览器中安装免费的 Alpine Linux 虚拟机,然后在虚拟机里面构建和运行 Docker 容器,甚至可以使用 Docker 集群模式创建集群。 有了 Docker-in-Docker(DinD)引擎,甚至可以体验到多个虚拟机/个人电脑的效果。 除了 Docker 游乐场外,PWD 还包括一个培训站点 training.play-with-docker.com,该站点提供大量的难度各异的 Docker 实验和测验。

如果你错过了峰会,Marcos 和 Jonathan 在最后一场 DockerCon Moby Cool Hack 会议中展示了 PWD。 观看下面的视频,深入了解其基础结构和发展路线图。

在过去几个月里,Docker 团队与 Marcos、Jonathan,还有 Docker 社区的其他活跃成员展开了密切合作,为项目添加了新功能,为培训部分增加了 Docker 实验室。

PWD: 游乐场

以下快速的概括了游乐场的新功能:

1、 PWD Docker Machine 驱动和 SSH

随着 PWD 成功的成长,社区开始问他们是否可以使用 PWD 来运行自己的 Docker 研讨会和培训。 因此,对项目进行的第一次改进之一就是创建 PWD Docker Machine 驱动,从而用户可以通过自己喜爱的终端轻松创建管理 PWD 主机,包括使用 SSH 相关命令的选项。 下面是它的工作原理:

Play With Docker

2、 支持文件上传

Marcos 和 Jonathan 还带来了另一个炫酷的功能就是可以在 PWD 实例中通过拖放文件的方式将 Dockerfile 直接上传到 PWD 窗口。

3、 模板会话

除了文件上传之外,PWD 还有一个功能,可以使用预定义的模板在几秒钟内启动 5 个节点的群集。

Play with Docker

4、 一键使用 Docker 展示你的应用程序

PWD 附带的另一个很酷的功能是它的内嵌按钮,你可以在你的站点中使用它来设置 PWD 环境,并快速部署一个构建好的堆栈,另外还有一个 chrome 扩展 ,可以将 “Try in PWD” 按钮添加 DockerHub 最流行的镜像中。 以下是扩展程序的一个简短演示:

Play with Docker

PWD 培训站点

training.play-with-docker.com 站点提供了大量新的实验。有一些值得注意的两点,包括两个来源于奥斯丁召开的 DockerCon 中的动手实践的实验,还有两个是 Docker 17.06CE 版本中亮眼的新功能:

总而言之,现在有 36 个实验,而且一直在增加。 如果你想贡献实验,请从查看 GitHub 仓库开始。

PWD 用例

根据网站访问量和我们收到的反馈,很可观的说,PWD 现在有很大的吸引力。下面是一些最常见的用例:

  • 紧跟最新开发版本,尝试新功能。
  • 快速建立集群并启动复制服务。
  • 通过互动教程学习: training.play-with-docker.com
  • 在会议和集会上做演讲。
  • 召开需要复杂配置的高级研讨会,例如 Jérôme’的 Docker 编排高级研讨会
  • 和社区成员协作诊断问题检测问题。

参与 PWD:


作者简介:

Victor 是 Docker, Inc. 的高级社区营销经理。他喜欢优质的葡萄酒、象棋和足球,上述爱好不分先后顺序。 Victor 的 tweet:@vcoisne。


via: https://blog.docker.com/2017/07/best-way-learn-docker-free-play-docker-pwd/

作者:Victor 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出