分类 技术 下的文章

在机器学习、计算机视觉以及高性能计算领域,充分利用显卡计算应用程序的能力已成为当前的热门。类似 OpenCL 的技术通过硬件无关的编程模型展现了这种能力,使得你可以编写抽象于不同体系架构的代码。它的目标是“一次编写,到处运行”,不管它是 Intel CPU、AMD 独立显卡还是 DSP 等等。不幸的是,对于日常程序员,OpenCL 的学习曲线陡峭;一个简单的 Hello World 程序可能就需要上百行晦涩难懂的代码。因此,为了减轻这种痛苦,Khronos 组织已经开发了一个称为 SYCL 的新标准,这是一个在 OpenCL 之上的 C++ 抽象层。通过 SYCL,你可以使用干净、现代的 C++ 开发出这些通用 GPU(GPGPU)应用程序,而无需拘泥于 OpenCL。下面是一个使用 SYCL 开发,通过并行 STL 实现的向量乘法事例:

#include <vector>
#include <iostream>

#include <sycl/execution_policy>
#include <experimental/algorithm>
#include <sycl/helpers/sycl_buffers.hpp>

using namespace std::experimental::parallel;
using namespace sycl::helpers;

int main() {
  constexpr size_t array_size = 1024*512;
  std::array<cl::sycl::cl_int, array_size> a;
  std::iota(begin(a),end(a),0);

  {
    cl::sycl::buffer<int> b(a.data(), cl::sycl::range<1>(a.size()));
    cl::sycl::queue q;
    sycl::sycl_execution_policy<class Mul> sycl_policy(q);
    transform(sycl_policy, begin(b), end(b), begin(b),
              [](int x) { return x*2; });
  }
}

为了作为对比,下面是一个通过 C++ API 使用 OpenCL 编写的大概对应版本(无需花过多时间阅读,只需注意到它看起来难看而且冗长)。

#include <iostream>
#include <array>
#include <numeric>
#include <CL/cl.hpp>

int main(){
    std::vector<cl::Platform> all_platforms;
    cl::Platform::get(&all_platforms);
    if(all_platforms.size()==0){
        std::cout<<" No platforms found. Check OpenCL installation!\n";
        exit(1);
    }
    cl::Platform default_platform=all_platforms[0];

    std::vector<cl::Device> all_devices;
    default_platform.getDevices(CL_DEVICE_TYPE_ALL, &all_devices);
    if(all_devices.size()==0){
        std::cout<<" No devices found. Check OpenCL installation!\n";
        exit(1);
    }

    cl::Device default_device=all_devices[0];
    cl::Context context({default_device});

    cl::Program::Sources sources;
    std::string kernel_code=
        "   void kernel mul2(global int* A){"
        "       A[get_global_id(0)]=A[get_global_id(0)]*2;"
        "   }";
    sources.push_back({kernel_code.c_str(),kernel_code.length()});

    cl::Program program(context,sources);
    if(program.build({default_device})!=CL_SUCCESS){
        std::cout<<" Error building: "<<program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(default_device)<<"\n";
        exit(1);
    }

    constexpr size_t array_size = 1024*512;
    std::array<cl_int, array_size> a;
    std::iota(begin(a),end(a),0);

    cl::Buffer buffer_A(context,CL_MEM_READ_WRITE,sizeof(int)*a.size());
    cl::CommandQueue queue(context,default_device);

    if (queue.enqueueWriteBuffer(buffer_A,CL_TRUE,0,sizeof(int)*a.size(),a.data()) != CL_SUCCESS) {
        std::cout << "Failed to write memory;n";
        exit(1);
    }

    cl::Kernel kernel_add = cl::Kernel(program,"mul2");
    kernel_add.setArg(0,buffer_A);

    if (queue.enqueueNDRangeKernel(kernel_add,cl::NullRange,cl::NDRange(a.size()),cl::NullRange) != CL_SUCCESS) {
        std::cout << "Failed to enqueue kernel\n";
        exit(1);
    }

    if (queue.finish() != CL_SUCCESS) {
        std::cout << "Failed to finish kernel\n";
        exit(1);
    }

    if (queue.enqueueReadBuffer(buffer_A,CL_TRUE,0,sizeof(int)*a.size(),a.data()) != CL_SUCCESS) {
        std::cout << "Failed to read result\n";
        exit(1);
    }
}

在这篇博文中我会介绍使用 SYCL 加速你 GPU 上的 C++ 代码。

GPGPU 简介

在我开始介绍如何使用 SYCL 之前,我首先给那些不熟悉这方面的人简要介绍一下为什么你可能想要在 GPU 上运行计算任务。如果已经使用过 OpenCL、CUDA 或类似的库,可以跳过这一节。

GPU 和 CPU 的一个关键不同就是 GPU 有大量小的、简单的处理单元,而不是少量(对于普通消费者桌面硬件通常是 1-8 个)复杂而强大的核。

CPU 架构

上面是一个 4 核 CPU 的简单漫画示意图。每个核都有一组寄存器以及不同等级的缓存(有些是共享缓存、有些不是),然后是主内存。

GPU 架构

在 GPU 上,多个小处理单元被组成一个执行单元。每个小处理单元都附有少量内存,每个执行单元都有一些共享内存用于它的处理单元。除此之外,还有一些 GPU 范围的内存,然后是 CPU 使用的主内存。执行单元内部的单元是 lockstep ,每个单元都在不同的数据片上执行相同的指令。

这可以使 GPU 同时处理大量的数据。如果是在 CPU 上,也许你可以使用多线程和向量指令在给定时间内完成大量的工作,但是 GPU 所能处理的远多于此。在 GPU 上一次性能够处理的数据规模使得它非常适合于类似图形(duh)、数学处理、神经网络等等。

GPGPU 编程的很多方面使得它和日常的 CPU 编程完全不同。例如,从主内存传输数据到 GPU 是很慢的真的很慢。会完全干掉你的性能使你慢下来。因此,GPU 编程的权衡是尽可能多地利用加速器的高吞吐量来掩盖数据来往的延迟。

这里还有一些不那么明显的问题,例如分支的开销。由于执行单元内的处理单元按照 lockstep 工作,使它们执行不同路径(不同的控制流)的嵌套分支就是个真正的问题。这通常通过在所有单元上执行所有分支并标记出无用结果来解决。这是一个基于嵌套级别的指数级的复杂度,这当然是坏事情。当然,有一些优化方法可以拯救该问题,但需要注意:你从 CPU 领域带来的简单假设和知识在 GPU 领域可能导致大问题。

在我们回到 SYCL 之前,需要介绍一些术语。 主机 host 是主 CPU 运行的机器, 设备 device 是会运行你 OpenCL 代码的地方。设备可能就是主机,但也可能是你机器上的一些加速器、模拟器等。 内核 kernel 是一个特殊函数,它是在你设备上运行代码的入口点。通常还会提供一些主机设置好的缓存给它用于输入和输出数据。

回到 SYCL

这里有两个可用的 SYCL 实现:triSYCL,由 Xilinx 开发的实验性开源版本(通常作为标准的试验台使用),以及 ComputeCpp,由 Codeplay(我在 Codeplay 工作,但这篇文章是在没有我雇主建议的情况下使用我自己时间编写的) 开发的工业级实现(当前处于开发测试版)。只有 ComputeCpp 支持在 GPU 上执行内核,因此在这篇文章中我们会使用它。

第一步是在你的机器上配置以及运行 ComputeCpp。主要组件是一个实现了 SYCL API 的运行时库,以及一个基于 Clang 的编译器,它负责编译你的主机代码和设备代码。在本文写作时,已经在 Ubuntu 和 CentOS 上官方支持 Intel CPU 以及某些 AMD GPU。在其它 Linux 发行版上让它工作也非常简单(例如,我让它在我的 Arch 系统上运行)。对更多的硬件和操作系统的支持正在进行中,查看支持平台文档获取最新列表。这里列出了依赖和组件。你也可能想要下载 SDK,其中包括了示例、文档、构建系统集成文件,以及其它。在这篇文章中我会使用 SYCL 并行 STL,如果你想要自己在家学习的话也要下载它。

一旦你设置好了一切,我们就可以开始通用 GPU 编程了!正如简介中提到的,我的第一个示例使用 SYCL 并行 STL 实现。我们现在来看看如何使用纯 SYCL 编写代码。

#include <CL/sycl.hpp>

#include <array>
#include <numeric>
#include <iostream>

int main() {
      const size_t array_size = 1024*512;
      std::array<cl::sycl::cl_int, array_size> in,out;
      std::iota(begin(in),end(in),0);

      {
          cl::sycl::queue device_queue;
          cl::sycl::range<1> n_items{array_size};
          cl::sycl::buffer<cl::sycl::cl_int, 1> in_buffer(in.data(), n_items);
          cl::sycl::buffer<cl::sycl::cl_int, 1> out_buffer(out.data(), n_items);

          device_queue.submit([&](cl::sycl::handler &cgh) {
              constexpr auto sycl_read = cl::sycl::access::mode::read;
              constexpr auto sycl_write = cl::sycl::access::mode::write;

              auto in_accessor = in_buffer.get_access<sycl_read>(cgh);
              auto out_accessor = out_buffer.get_access<sycl_write>(cgh);

              cgh.parallel_for<class VecScalMul>(n_items,
                  [=](cl::sycl::id<1> wiID) {
                      out_accessor[wiID] = in_accessor[wiID]*2;
                  });
         });
     }
}

我会把它划分为一个个片段。

#include <CL/sycl.hpp>

我们做的第一件事就是包含 SYCL 头文件,它会在我们的命令中添加 SYCL 运行时库。

const size_t array_size = 1024*512;
std::array<cl::sycl::cl_int, array_size> in,out;
std::iota(begin(in),end(in),0);

这里我们构造了一个很大的整型数组并用数字 0array_size-1 初始化(这就是 std::iota 所做的)。注意我们使用 cl::sycl::cl_int 确保兼容性。

{
    //...
}

接着我们打开一个新的作用域,其目的为二:

  1. device_queue 将在该作用域结束时解构,它将阻塞,直到内核完成。
  2. in_bufferout_buffer 也将解构,这将强制数据传输回主机并允许我们从 inout 中访问数据。 cl::sycl::queue device_queue;

现在我们创建我们的命令队列。命令队列是所有工作(内核)在分发到设备之前需要入队的地方。有很多方法可以定制队列,例如说提供设备用于入队或者设置异步错误处理器,但对于这个例子默认构造器就可以了;它会查找兼容的 GPU,如果失败的话会回退到主机 CPU。

cl::sycl::range<1> n_items{array_size};

接下来我们创建一个范围,它描述了内核在上面执行的数据的形状。在我们简单的例子中,它是一个一维数组,因此我们使用 cl::sycl::range<1>。如果数据是二维的,我们就会使用 cl::sycl::range<2>,以此类推。除了 cl::sycl::range,还有 cl::sycl::ndrange,它允许你指定工作组大小以及越界范围,但在我们的例子中我们不需要使用它。

cl::sycl::buffer<cl::sycl::cl_int, 1> in_buffer(in.data(), n_items);
cl::sycl::buffer<cl::sycl::cl_int, 1> out_buffer(out.data(), n_items);

为了控制主机和设备之间的数据共享和传输,SYCL 提供了一个 buffer 类。我们创建了两个 SYCL 缓存用于管理我们的输入和输出数组。

      device_queue.submit([&](cl::sycl::handler &cgh) {/*...*/});

设置好了我们所有数据之后,我们就可以入队真正的工作。有多种方法可以做到,但设置并行执行的一个简单方法是在我们的队列中调用 .submit 函数。对于这个函数我们传递了一个运行时调度该任务时会被执行的“命令组伪函数”(伪函数是规范,不是我创造的)。命令组处理器设置任何内核需要的余下资源并分发它。

constexpr auto sycl_read = cl::sycl::access::mode::read_write;
constexpr auto sycl_write = cl::sycl::access::mode::write;

auto in_accessor = in_buffer.get_access<sycl_read>(cgh);
auto out_accessor = out_buffer.get_access<sycl_write>(cgh);

为了控制到我们缓存的访问并告诉该运行时环境我们会如何使用数据,我们需要创建访问器。很显然,我们创建了一个访问器用于从 in_buffer 读入,一个访问器用于写到 out_buffer

cgh.parallel_for<class VecScalMul>(n_items,
    [=](cl::sycl::id<1> wiID) {
         out_accessor[wiID] = in_accessor[wiID]*2;
    });

现在我们已经完成了所有设置,我们可以真正的在我们的设备上做一些计算了。这里我们根据范围 n_items 在命令组处理器 cgh 之上分发一个内核。实际内核自身是一个使用 work-item 标识符作为输入、输出我们计算结果的 lamda 表达式。在这种情况下,我们从 in_accessor 使用 work-item 标识符作为索引读入,将其乘以 2,然后将结果保存到 out_accessor 相应的位置。<class VecScalMul> 是一个为了在标准 C++ 范围内工作的不幸的副产品,因此我们需要给内核一个唯一的类名以便编译器能完成它的工作。

}

在此之后,我们现在可以访问 out 并期望看到正确的结果。

这里有相当多的新概念在起作用,但使用这些技术你可以看到这些能力和所展现出来的东西。当然,如果你只是想在你的 GPU 上执行一些代码而不关心定制化,那么你就可以使用 SYCL 并行 STL 实现。

SYCL 并行 STL

SYCL 并行 STL 是一个 TS 的并行化实现,它分发你的算法函数对象作为 SYCL 内核。在这个页面前面我们已经看过这样的例子,让我们来快速过一遍。

#include <vector>
#include <iostream>

#include <sycl/execution_policy>
#include <experimental/algorithm>
#include <sycl/helpers/sycl_buffers.hpp>

using namespace std::experimental::parallel;
using namespace sycl::helpers;

int main() {
  constexpr size_t array_size = 1024*512;
  std::array<cl::sycl::cl_int, array_size> in,out;
  std::iota(begin(in),end(in),0);

  {
    cl::sycl::buffer<int> in_buffer(in.data(), cl::sycl::range<1>(in.size()));
    cl::sycl::buffer<int> out_buffer(out.data(), cl::sycl::range<1>(out.size()));
    cl::sycl::queue q;
    sycl::sycl_execution_policy<class Mul> sycl_policy(q);
    transform(sycl_policy, begin(in_buffer), end(in_buffer), begin(out_buffer),
              [](int x) { return x*2; });
  }
}
  constexpr size_t array_size = 1024*512;
  std::array<cl::sycl::cl_int, array_size> in, out;
  std::iota(begin(in),end(in),0);

到现在为止一切如此相似。我们再一次创建一组数组用于保存我们的输入输出数据。

cl::sycl::buffer<int> in_buffer(in.data(), cl::sycl::range<1>(in.size()));
cl::sycl::buffer<int> out_buffer(out.data(), cl::sycl::range<1>(out.size()));
cl::sycl::queue q;

这里我们创建类似上个例子的缓存和队列。

sycl::sycl_execution_policy<class Mul> sycl_policy(q);

这就是有趣的部分。我们从我们的队列中创建 sycl_execution_policy,给它一个名称让内核使用。这个执行策略然后可以像 std::execution::parstd::execution::seq 那样使用。

transform(sycl_policy, begin(in_buffer), end(in_buffer), begin(out_buffer),
          [](int x) { return x*2; });

现在我们的内核分发看起来像提供了一个执行策略的 std::transform 调用。我们传递的闭包会被编译并在设备上执行,而不需要我们做其它更加复杂的设置。

当然,除了 transform 你可以做更多。开发的时候,SYCL 并行 STL 支持以下算法:

  • sort
  • transform
  • for_each
  • for_each_n
  • count_if
  • reduce
  • inner_product
  • transform_reduce

这就是这篇短文需要介绍的东西。如果你想和 SYCL 的开发保持同步,那就要看 sycl.tech。最近重要的开发就是移植 EigenTensorflow 到 SYCL ,为 OpenCL 设备带来引入关注的人工智能编程。对我个人而言,我很高兴看到高级编程模型可以用于异构程序自动优化,以及它们是怎样支持类似 HPXSkelCL 等更高级的技术。


via: https://blog.tartanllama.xyz/c++/2017/05/19/sycl/

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

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

学习 GRUB 引导加载程序是如何预备你的系统并启动操作系统内核的。

 title=

自从上个月为我的文章《Linux 引导和启动过程简介》做研究开始,我对更深入了解 GRUB2 产生了兴趣。这篇文章提供了配置 GRUB2 的简要介绍。为了简便起见,我大多数情况下会使用 GRUB 指代 GRUB2。

GRUB

GRUB 来自 GRand Unified Bootloader 的缩写。它的功能是在启动时从 BIOS 接管掌控、加载自身、加载 Linux 内核到内存,然后再把执行权交给内核。一旦内核开始掌控,GRUB 就完成了它的任务,也就不再需要了。

GRUB 支持多种 Linux 内核,并允许用户在启动时通过菜单在其中选择。我发现这是一种非常有用的工具,因为我有很多次遇到一个应用程序或者系统服务在特定内核版本下失败的问题。有好几次,引导到一个较旧的内核时就可以避免类似的问题。默认情况下,使用 yumdnf 进行更新时会保存三个内核 - 最新的以及两个比较旧的。在被包管理器删除之前所保留的内核数目可以在 /etc/dnf/dnf.conf/etc/yum.conf 文件中配置。我通常把 installonly_limit 的值修改为 9 以便保留 9 个内核。当我不得不恢复到低几个版本的内核时这非常有用。

GRUB 菜单

GRUB 菜单的功能是当默认的内核不是想要的时,允许用户从已经安装的内核中选择一个进行引导。通过上下箭头键允许你选中想要的内核,敲击回车键会使用选中的内核继续引导进程。

GRUB 菜单也提供了超时机制,因此如果用户没有做任何选择,GRUB 就会在没有用户干预的情况下使用默认内核继续引导。敲击键盘上除了回车键之外的任何键会停止终端上显示的倒数计时器。立即敲击回车键会使用默认内核或者选中的内核继续引导进程。

GRUB 菜单提供了一个 “ 救援 rescue ” 内核,用于故障排除或者由于某些原因导致的常规内核不能完成启动过程。不幸的是,这个救援内核不会引导到救援模式。文章后面会更详细介绍这方面的东西。

grub.cfg 文件

grub.cfg 文件是 GRUB 配置文件。它由 grub2-mkconfig 程序根据用户的配置使用一组主配置文件以及 grub 默认文件而生成。/boot/grub2/grub.cfg 文件在 Linux 安装时会初次生成,安装新内核时又会重新生成。

grub.cfg 文件包括了类似 Bash 脚本的代码以及一个按照安装顺序排序的已安装内核列表。例如,如果你有 4 个已安装内核,最新的内核索引是 0,前一个内核索引是 1,最旧的内核索引是 3。如果你能访问 grub.cfg 文件,你应该去看看感受一下它看起来是什么样。grub.cfg 太大也就没有包含在这篇文章中。

GRUB 配置文件

grub.cfg 的主要配置文件都在 /etc/grub.d 目录。该目录中的每个文件都包含了最终会整合到 grub.cfg 文件中的 GRUB 代码。这些配置文件的命名模式以排序方式设计,这使得最终的 grub.cfg 文件可以按正确的顺序整合而成。每个文件都有注释表明该部分的开始和结束,这些注释也是最终的 grub.cfg 文件的一部分,从而可以看出每个部分是由哪个文件生成。分隔注释看起来像这样:

### BEGIN /etc/grub.d/10_linux ###

### END /etc/grub.d/10_linux ###

不要修改这些文件,除非你是一个 GRUB 专家并明白更改会发生什么。无论如何,修改 grub.cfg 文件时你也总应该保留一个原始文件的备份。 40_custom41_custom 这两个特别的文件用于生成用户对 GRUB 配置的修改。你仍然要注意对这些文件的更改的后果,并保存一份原始 grub.cfg 文件的备份。

你也可以把你自己的文件添加到 /etc/grub.d 目录。这样做的一个可能的原因是为非 Linux 操作系统添加菜单行。要注意遵循命名规则,确保配置文件中额外的菜单选项刚好在 10_linux 条目之前或之后。

GRUB 默认文件

老版本 GRUB 的配置非常简单而明了,我只需要修改 /boot/grub/grub.conf 就可以了。对于新版本的 GRUB2,我虽然还可以通过更改 /boot/grub2/grub.cfg 来修改,但和老版本的 GRUB 相比,新版本相对更加复杂。另外,安装一个新内核时 grub.cfg 可能会被重写,因此任何修改都可能消失。当然,GNU.org 的 GRUB 手册确实有过直接创建和修改 /boot/grub2/grub.cfg 的讨论。

一旦你明白了如何做,更改 GRUB2 配置就会变得非常简单。我为之前的文章研究 GRUB2 的时候才明白这个。秘方就在 /etc/default 目录里面,一个自然而然称为 grub 的文件,它可以通过简单的终端命令操作。/etc/default 目录包括了一些类似 Google Chrome、 useradd、 和 grub 程序的配置文件。

/etc/default/grub 文件非常简单。这个 grub 默认文件已经列出了一些有效的键值对。你可以简单地更改现有键值或者添加其它文件中还没有的键。下面的列表 1 显示了一个没有更改过的 /etc/default/grub 文件。

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' 
   /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora_fedora25vm/root 
   rd.lvm.lv=fedora_fedora25vm/swap 
   rd.lvm.lv=fedora_fedora25vm/usr rhgb quiet"
GRUB_DISABLE_RECOVERY="true"

列表 1:Fedora 25 一个原始 grub 默认文件。

GRUB 手册 5.1 章节包括了所有可以添加到该 grub 文件的键的信息。我只需要修改 grub 默认文件已经有的一些键值就够了。让我们看看这些键值以及一些在 grub 默认文件中没有出现的每个键的意义。

  • GRUB_TIMEOUT 这个键的值决定了显示 GRUB 选择菜单的时间长度。GRUB 提供了同时保存多个安装内核并在启动时使用 GRUB 菜单在其中选择的功能。这个键的默认值是 5 秒,但我通常修改为 10 秒使得有更多时间查看选项并作出选择。
  • GRUB_DISTRIBUTOR 这个键定义了一个从 /etc/system-release 文件中提取发行版本的 sed 表达式。这个信息用于生成出现在 GRUB 菜单中的每个内核发布版的文本名称,例如 “Fedora” 等。由于不同发行版之间 system-release 文件结构的差异,在你的系统中这个 sed 表达式可能有些不同。
  • GRUB_DEFAULT 决定默认引导哪个内核。如果是 saved,这代表最新内核。这里的其它选项如果是数字则代表了 grub.cfg 中列表的索引。使用索引号 3,就会总是加载列表中的第四个内核,即使安装了一个新内核之后也是。因此使用索引数字的话,在安装一个新内核后会加载不同的内核。要确保引导特定内核版本的唯一方法是设置 GRUB_DEFAULT 的值为想要内核的名称,例如 4.8.13-300.fc25.x86_64
  • GRUB_SAVEDEFAULT 通常,grub 默认文件中不会指定这个选项。当选择不同内核进行引导时,正常操作下该内核只会启动一次。默认内核不会改变。当其设置为 true 并和 GRUB_DEFAULT=saved 一起使用时,这个选项会保存一个不同内核作为默认值。当选择不同内核进行引导时会发生这种情况。
  • GRUB_DISABLE_SUBMENU 一些人可能会希望为 GRUB 菜单创建一个内核的层级菜单结构。这个键和 grub.cfg 中一些额外内核配置允许创建这样的层级结构。例如,主菜单中可能有 productiontest 子菜单,每个子菜单中包括了一些合适的内核。设置它为 false 可以启用子菜单。
  • GRUB_TERMINAL_OUTPUT 一些环境下可能需要或者必要将输出重定向到一个不同的显示控制台或者终端。默认情况下是把输出发送到默认终端,通常 console 等价于 Intel 系列个人电脑的标准输出。另一个有用的选择是在使用串行终端或者 Integrated Lights Out (ILO) 终端连接的数据中心或者实验室环境中指定 serial
  • GRUB_TERMINAL_INPUTGRUB_TERMINAL_OUTPUT 类似,可能需要或者必要重定向输入为串行终端或者 ILO 设备、而不是标准键盘输入。
  • GRUB_CMDLINE_LINUX 这个键包括了在启动时会传递给内核的命令行参数。注意这些参数会被添加到 grub.cfg 所有已安装内核的内核行。这意味着所有已安装的内核在启动时都会有相同的参数。我通常删除 rhgbquiet 参数以便我可以看到引导和启动时内核和 systemd 输出的所有内核信息消息。
  • GRUB_DISABLE_RECOVERY 当这个键的值被设置为 false,GRUB 菜单中就会为每个已安装的内核创建一个恢复条目。当设置为 true 时就不会创建任何恢复条目。但不管这个设置怎样,最后的内核条目总是一个 rescue 选项。不过在 rescue 选项中我遇到了一个问题,下面我会详细介绍。

还有一些你可能觉得有用但我没有在这里介绍的键。它们的描述可以在 GRUB 手册 2 的 5.1 章节找到。

生成 grub.cfg

完成所需的配置之后,就需要生成 /boot/grub2/grub.cfg 文件。这通过下面的命令完成。

grub2-mkconfig > /boot/grub2/grub.cfg

这个命令按照顺序使用位于 /etc/grub.d 的配置文件构建 grub.cfg 文件,然后使用 grub 默认文件的内容修改输出以便获得最终所需的配置。grub2-mkconfig 命令会尝试定位所有已安装的内核并在 grub.cfg 文件的 10_Linux 部分新建条目。它还创建一个 rescue 条目提供一个用于从 Linux 不能启动的严重问题中恢复的方法。

强烈建议你不要手动编辑 grub.cfg 文件,因为任何对该文件的直接修改都会在下一次安装新内核或者手动运行 grub2-mkconfig 时被重写。

问题

我遇到一个如果没有意识到就可能导致严重后果的 GRUB2 问题。这个救援内核没有启动,反而启动了另外一个内核。我发现那是列表中索引为 1 的内核,也就是列表中的第二个内核。额外的测试发现不管使用原始的还是我生成的 grub.cfg 配置文件都会发生这个问题。我在虚拟机和真实硬件上都尝试过而且都发生了这个问题。我只测试了 Fedora 25,因此其它 Fedora 发行版本可能没有这个问题。

注意,从救援内核生成的 “recovery” 内核条目不能引导到维护模式。

我推荐将 grub 默认文件中 GRUB_DISABLE_RECOVERY 的值更改为 “false”,然后生成你自己的 grub.cfg。这会在 GRUB 菜单中为每个已安装的内核生成可用的恢复条目。这些恢复配置能像期望那样工作,从而从那些需要输入密码登录的内核条目中引导到运行级别 1,也就是进入(不需要密码的)单用户维护模式。你也可以按 Ctrl-D 继续正常的引导进入默认运行级别。

总结

GRUB 是引导 Linux 计算机到可用状态过程的一系列事件中,发生在 BIOS 之后的第一步。理解如何配置 GRUB 对于恢复或者处理多种类型的问题非常重要。

这么多年来我多次不得不引导到恢复或者救援模式以便解决多种类型的问题。其中的一些问题确实是类似 /etc/fstab 或其它配置文件中不恰当条目导致的引导问题,也有一些是由于应用程序或者系统软件和最新的内核不兼容的问题。硬件兼容性问题也可能妨碍特定的内核启动。

我希望这些信息能对你开启 GRUB 配置之旅有所帮助。

( 题图 : Internet Archive Book Images. Opensource.com 修改。 CC BY-SA 4.0)


作者简介:

David Both - David Both 是一个居住在 Raleigh,北卡罗来纳州的 Linux 和开源倡导者。他在 IT 界已经有超过 40 年,并在他工作的 IBM 执教 OS/2 20 多年。在 IBM 的时候,他在 1981 年开设了第一个最初 IBM 个人电脑的培训课程。他在红帽教授过 RHCE 课程并在 MCI Worldcom、 Cisco、 和北卡罗来纳州工作过。他已经在 Linux 和开源软件方面工作将近 20 年。


via: https://opensource.com/article/17/3/introduction-grub2-configuration-linux

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

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

使用 7 条简单的 Git 命令开始你的软件开发之旅

你是否曾经想知道如何学好 Git?你长期以来都是跌跌撞撞地在使用 Git。最终,你总需要掌握它的窍门。这就是我写这篇文章的原因,我将带你去启蒙之旅。这儿是我关于如何加快 Git 学习过程的基本指南。我将介绍 Git 的实际情况以及我使用最多的 7 条 Git 命令。本文主要针对有兴趣的开发人员和大学新生,他们需要关于 Git 的介绍以及如何掌握基础知识。


你可以往前继续阅读整篇文章,或者只读 TLDR; 部分,尽管这将使我很受伤。

TLDR;

在学习 Git 的过程中,请养成下面这些步骤的习惯:

  1. 随时使用 git status
  2. 只更改那些你真正想更改的文件。
  3. git add -A 会是你的朋友。
  4. 随时使用命令 git commit -m "meaningful messages"
  5. 做任何推送(push)之前先使用命令 git pull,但是这需要在你提交过一些更改之后。
  6. 最后,git push推送提交的更改。

良宵莫辜负

对任何开发人员来说,通常第一步都是选择一个广泛使用的地方托管他或她的代码库。那就是,GitHub。它是一切有关代码的聚集地。要理解 GitHub 的概念,你先需要知道什么是 Git。

Git 是一款基于命令行的版本控制软件,在 Windows 和 Mac 系统上也有几款可用的桌面应用。 Git 由 Linux 之父 Linus Torvalds 开发,Linus Torvalds 还是是计算机科学中最有影响力的人物之一。因为这一优势,Git 已经成为绝大多数软件开发人员关于共享和维护代码的标准。这一大段话,让我们将其细细道来。正如它的名字所说,版本控制软件 Git 让你可以预览你写过的代码的所有版本。从字面上来说, 开发人员的每个代码库都将永远存储在其各自的仓库中,仓库可以叫做任何名字,从 pineappleexpress 都行。在此仓库开发代码的过程中,你将进行出无数次的更改,直到第一次正式发布。这就是版本控制软件如此重要的核心原因所在。它让作为开发人员的你可以清楚地了解对代码库进行的所有更改、修订和改进。从另外一个方面说,它使协同合作更容易,下载代码进行编辑,然后将更改上传到仓库。然而,尽管有了这么多好处,然而还有一件事可以锦上添花。你可以下载并使用这些文件,即使你在整个开发过程中什么事也没有做。

让我们回到文章的 GitHub 部分。它只是所有仓库的枢纽(hub),这些仓库可以存储在其中并在线浏览。它是一个让有着共同兴趣的人相聚的地方。

千里之行始于足下

OK,记住,Git 是一款软件,像任何其他软件一样,你首先需要安装它:

Git - 安装 Git,如果你希望从源代码安装 Git,你需要安装这些 Git 的依赖库: autotools —— 来自 git-scm.com

Tips:请点击上面的链接,然后按照说明开始。

完成了安装过程,很好。现在你需要在你的浏览器地址栏输入 github.com 访问该网站。如果你还没有帐号的话需要新创建一个帐号,这就是你的起舞之处。登录并创建一个新仓库,命名为 Steve ,没有什么理由,只是想要一个名为史蒂夫的仓库好玩而已。选中 “Initialize this repository with a README” 复选框并点击创建按钮。现在你有了一个叫做 Steve 的仓库。我相信你会为你自己感到自豪。

现在开始在使用 Git

现在是比较有趣的部分。你将把 Steve 克隆到你本地的机器上。可以把这个过程看作从 Github 上复制仓库到你的电脑上。点击 “clone or download” 按钮,你将看到一个类似下面这样的 URL:

https://github.com/yourGithubAccountName/Steve.git

复制这个 URL 并打开命令提示符窗口。现在输入并运行条命令:

git clone https://github.com/yourGithubAccountName/Steve.git

Abrakadabra!Steve 仓库已经被自动克隆到了你的电脑上。查看你克隆这个仓库的目录,你会看到一个叫做 Steve 的文件夹。这个本地的文件夹现在已经链接到了它的 “origin” ,也就是 GitHub 上的远程仓库。

记住这个过程,在你的软件开发工程人员的职业生涯中你一定会重复这个过程很多次的。完成所有这些准备工作之后,你就可以开始使用最普通且常用的 Git 命令了。

你现在已经开始在真实场景使用 Git 了

找到 Steve 目录并在该目录中打开命令提示符窗口,运行下面的命令:

git status

这会输出你的工作目录的状态,让你知道所有你编辑过的文件。这意味着它显示了远程库中和本地工作目录中之间的文件差异。status 命令被用来作为 commit 的模版,我将在这篇教程后面进一步谈论 commit 。简单的说,[git status][1] 告诉你你编辑过哪些文件,以给你一个你想要上传到远程库的概述。

但是,在你做任何上传之前,你首先需要做的是选择你需要发送回远程库的文件。使用下面命令完成:

git add

接着在 Steve 目录新建一个文本文件,可以取一个好玩的名字 pineapple.txt。在这个文件里面随便写些你想写的内容,返回命令提示符,然后再次输入 git status。现在,你将看到这个文件以红色出现在标记 “untracked files” 下面。

On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be commited)

pineapple.txt

下一步就是将它添加到暂存区(staging)。暂存区可以看作是这样的一个环境:你做过的所有更改在提交时都将捆绑为一个更改而被提交。现在,你可以将这个文件加入暂存区:

git add -A

-A 选项意味着所有你更改过的文件都会被加到暂存区等待提交。然而, git add 非常灵活,它也可以像这样一个文件一个文件的添加:

git add pineapple.txt

这种方法让你有能力选择你想要暂存的每一个文件,而不用担心加入那些你不想改变的东西。

再次运行 git status,你会看到如下输出:

On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

new file:   pineapple.txt

准备好提交更改了吗?开始吧。

git commit -m "Write your message here"

Git commit 命令会将存储在暂存区中的文件和来自用户的用于描述更改的日志信息一起存储在一个新的地方。-m选项加入了写在双引号内的信息。

再次检查状态,你会看到:

On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean

所有的更改现在都被加入到一次提交当中了,同时会有一条与你所做相关的信息。现在你可以用 git push 将这次提交推送到远程库 “origin”了。这条命令就像字面意义所说,它会把你提交的更改从本地机器上传到 GitHub 的远程仓库中。返回到命令提示符,然后运行:

git push

你会被要求输入你的 GitHub 帐号和密码,之后你会看到类似下面的这些内容:

Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 280 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/yourGithubUserName/Steve.git
   c77a97c..08bb95a  master -> master

就是这样。你已经成功上传了你本地的更改。看看你在 GitHub 上的仓库,你会看到它现在包含了一叫做 pineapple.txt 的文件。

如果你是一个开发小组的一员呢?如果他们都推送提交到 “origin”,将会发生什么?这就是 Git 真正开始发挥它的魔力的时候。你可以使用一条简单的命令轻松地将最新版本的代码库 pull 到你本地的机器上:

git pull

但是 Git 也有限制:你需要有相匹配的版本才能推送到 “origin”。这意味着你本地的版本需要和 origin 的版本大致一样。当你从 “origin” 拉取(pull)文件时,在你的工作目录中不能有文件,因为它们将会在这个过程中被覆盖。因此我给出了这条简单的建议。在学习 Git 的过程中,请养成下面这些步骤的习惯:

  1. 随时使用 git status
  2. 只更改那些你真正想更改的文件。
  3. git add -A 会是你的朋友。
  4. 随时使用命令 git commit -m "meaningful messages"
  5. 做任何推送(push)之前先使用命令 git pull,但是这需要在你提交过一些更改之后。
  6. 最后,git push推送提交的更改。

嘿!你还在看这篇文章吗?你已经看了很久了,休息一下吧!

休息好了吗?好的!让我们来处理一些错误。如果你不小心更改了一些你本不应该更改的文件后怎么办呢?不需要担心,只需要使用 git checkout。让我们在文件 pineapple.txt 里更改一些内容:在文件中加入一行,比方说,“Steve is mega-awesome!” 。然后保存更改并用 git status 检查一下:

On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   pineapple.txt

no changes added to commit (use "git add" and/or "git commit -a")

正如预料的那样,它已经被记录为一次更改了。但是,假如 Steve 实际上并不是很优秀呢?假如 Steve 很差劲呢?不用担心!最简单的还原更改的方式是运行命令:

git checkout -- pineapple.txt

现在你会看到文件已经恢复到了先前的状态。

但是假如你玩砸了呢?我是说,事情已经变得混乱,并且需要把所有东西重置到与 “origin” 一样的状态。也不需要担心,在这种紧急情况下我们可以享受 Git 的美妙之处:

git reset --hard

git reset 命令和 --hard 选项一起可以抛弃自上次提交以来的所有更改,有些时候真的很好用。


最后,我想鼓励你尽可能多地使用 Git。这是能够熟练使用它的最好学习方式。除此之外,养成阅读 Git 文档的习惯。一开始可能会有些云里雾里,但是过段时间后你就会明白它的窍门了。

希望你们(小伙子和姑娘们)读这篇文章的时候会和我写它时一样的开心。如果你认为这篇文章对别人有用,你尽可以与别人分享它。或者,如果你喜欢这篇文章,你可以在下面点下赞以便让更多的人看到这篇文章。


via: https://hackernoon.com/how-to-master-the-art-of-git-68e1050f3147

作者:Adnan Rahić 译者:zhousiyu325 校对:wxy

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

Samba 很久以来一直是为 *nix 系统上的 Windows 客户端提供共享文件和打印服务的标准。家庭用户,中型企业和大型公司都在使用它,它作为最佳解决方案在多种操作系统共存的环境中脱颖而出。

由于广泛使用的工具很可能发生这种情况,大多数 Samba 安装都面临着可能利用已知漏洞的攻击的风险,这个漏洞直到 WannaCry 勒索软件攻击的新闻出来之前都被认为是不重要的。

在本文中,我们将解释这个 Samba 漏洞是什么以及如何保护你负责的系统。根据你的安装类型(从仓库或者源码),你需要采取不同的方法。

如果你目前有在任何环境中使用 Samba 或者知道有人在使用,请继续阅读!

漏洞

过时和未修补的系统容易受到远程代码执行漏洞的攻击。简单来说,这意味着访问可写共享的人可以上传一段任意代码,并使用服务器中的 root 权限执行该代码。

这个问题在 Samba 网站上被描述为 CVE-2017-7494,并且已知会影响 Samba v3.5(2010 年 3 月初发布)及以后版本。由于与 WannaCry 有相似之处,它被非官方地被命名为 SambaCry:它们均针对 SMB 协议,并且可能是蠕虫病毒 - 这可能导致其从一个系统传播到另一个系统中。

Debian、Ubuntu、CentOS 和 Red Hat 已采取快速的行动来保护它们的用户,并为其支持的版本发布了补丁。另外,还提供了不受支持的安全临时解决方案。

更新 Samba

如先前提到的那样,根据你之前安装的方法有两种方式更新:

如果你从发行版的仓库中安装的 Samba

让我们看下在这种情况下你需要做什么:

在 Debian 下修复 SambaCry

添加下面的行到你的源列表中(/etc/apt/sources.list)以确保 apt 能够获得最新的安全更新:

deb http://security.debian.org stable/updates main
deb-src http://security.debian.org/ stable/updates main

接下来,更新可用的软件包:

# aptitude update

最后,确保 samba 软件包的版本符合漏洞修复的版本(见 CVE-2017-7494):

# aptitude show samba

Fix Sambacry in Debian

在 Debian 中修复 SambaCry

在 Ubuntu 中修复 SambaCry

要开始修复,如下检查新的可用软件包并更新 Samba 软件包:

$ sudo apt-get update
$ sudo apt-get install samba

已经修复 CVE-2017-7494 的 Samba 版本有下面这些:

  • 17.04: samba 2:4.5.8+dfsg-0ubuntu0.17.04.2
  • 16.10: samba 2:4.4.5+dfsg-2ubuntu5.6
  • 16.04 LTS: samba 2:4.3.11+dfsg-0ubuntu0.16.04.7
  • 14.04 LTS: samba 2:4.3.11+dfsg-0ubuntu0.14.04.8

最后,运行下面命令验证你的 Ubuntu 已经安装了正确的版本。

$ sudo apt-cache show samba

在 CentOS/RHEL 7 中修复 SambaCry

在 EL 7 中打过补丁的 Samba 版本是 samba-4.4.4-14.el7\_3。要安装它,这些做:

# yum makecache fast
# yum update samba

像先前那样,确保你已经安装了打补丁的 Samba 版本:

# yum info samba

Fix Sambacry in CentOS

在 CentOS 中修复 SambaCry

旧支持的 CentOS 以及 RHEL 更老的版本也有修复。参见 RHSA-2017-1270 获取更多。

如果你从源码安装的 Samba

注意:下面的过程假设你先前从源码构建的 Samba。强烈建议你在部署到生产服务器之前先在测试环境尝试。

此外,开始之前确保你备份了 smb.conf 文件。

在这种情况下,我们也会从源码编译并更新 Samba。然而在开始之前,我们必须先确保安装了所有的依赖。注意这也许会花费几分钟。

在 Debian 和 Ubuntu 中:

# aptitude install acl attr autoconf bison build-essential \
debhelper dnsutils docbook-xml docbook-xsl flex gdb krb5-user \
libacl1-dev libaio-dev libattr1-dev libblkid-dev libbsd-dev \
libcap-dev libcups2-dev libgnutls28-dev libjson-perl \
libldap2-dev libncurses5-dev libpam0g-dev libparse-yapp-perl \
libpopt-dev libreadline-dev perl perl-modules pkg-config \
python-all-dev python-dev python-dnspython python-crypto xsltproc \
zlib1g-dev libsystemd-dev libgpgme11-dev python-gpgme python-m2crypto

在 CentOS 7 或相似的版本中:

# yum install attr bind-utils docbook-style-xsl gcc gdb krb5-workstation \
libsemanage-python libxslt perl perl-ExtUtils-MakeMaker \
perl-Parse-Yapp perl-Test-Base pkgconfig policycoreutils-python \
python-crypto gnutls-devel libattr-devel keyutils-libs-devel \
libacl-devel libaio-devel libblkid-devel libxml2-devel openldap-devel \
pam-devel popt-devel python-devel readline-devel zlib-devel

停止服务(LCTT 译注:此处不必要):

# systemctl stop smbd

下载并解压源码(在写作时 4.6.4 是最新的版本):

# wget https://www.samba.org/samba/ftp/samba-latest.tar.gz 
# tar xzf samba-latest.tar.gz
# cd samba-4.6.4

出于了解信息的目的,用以下命令检查可用的配置选项。

# ./configure --help

如果你在先前的版本的构建中有使用到一些选项,你或许可以在上面命令的返回中包含一些选项,或者你可以选择使用默认值:

# ./configure
# make
# make install

最后重启服务。

# systemctl restart smbd

并验证你正在使用的是更新后的版本:

# smbstatus --version

这里返回的应该是 4.6.4。

其它情况

如果你使用的是不受支持的发行版本,并且由于某些原因无法升级到最新版本,你或许要考虑下面这些建议:

  • 如果 SELinux 是启用的,你是处于保护之下的!
  • 确保 Samba 共享是用 noexec 选项挂载的。这会阻止二进制文件从被挂载的文件系统中执行。

还有将:

nt pipe support = no

添加到 smb.conf[global] 字段中。你或许要记住,根据 Samba 项目,这“或许禁用 Windows 客户端的某些功能”。

重要:注意 nt pipe support = no 选项会禁用 Windows 客户端的共享列表。比如:当你在一台 Samba 服务器的 Windows Explorer 中输入 \\10.100.10.2\ 时,你会看到 “permission denied”。Windows 客户端不得不手动执行共享,如 \\10.100.10.2\share_name 来访问共享。

总结

在本篇中,我们已经描述了 SambaCry 漏洞以及如何减轻影响。我们希望你可以使用这个信息来保护你负责的系统。

如果你有关于这篇文章的任何提问以及评论,欢迎使用下面的评论栏让我们知道。


作者简介:

Gabriel Cánepa 是一名 GNU/Linux 系统管理员,阿根廷圣路易斯 Villa Mercedes 的 web 开发人员。他为一家国际大型消费品公司工作,在日常工作中使用 FOSS 工具以提高生产力,并从中获得极大乐趣。


via: https://www.tecmint.com/fix-sambacry-vulnerability-cve-2017-7494-in-linux/

作者:Gabriel Cánepa 译者:geekpi 校对:wxy

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

在本篇中,我们会解释 cron 和 anacron,并向你展示如何在 Linux 中设置 anacron。我们也会比较这两个工具。

在一个给定时间或者稍后安排一个任务,你可以使用 at 或者 batch 命令,要使命令能够重复运行,你可以使用 cron 以及 anacron 工具。

cron - 是一个用于运行计划任务如系统备份、更新等的守护进程。它适合在那些 24X7 不间断运行的机器如服务器上运行的计划任务。

命令/脚本被写在 cron 任务脚本中,它是在 crontab 文件中被安排的。系统默认的 crontab 文件是 /etc/crontab,但是每个用户也可以创建自己的 crontab 文件来在特定时间运行用户定义的命令。

要创建一份个人 crontab 文件,只要输入:

$ crontab -e

如何在 Linux 中设置 anacron

anacron 用于以天为单位的频率运行命令。它的工作与 cron 稍有不同,它假设机器不会一直开机

cron 也适合在那些不会 24X7 运行如笔记本以及桌面电脑的机器上运行每日、每周以及每月的计划任务(LCTT 译注:不适合按小时、分钟执行任务)。

假设你有一个计划任务(比如备份脚本)要使用 cron 在每天半夜运行,也许你以及睡着,那时你的桌面/笔记本电脑已经关机。你的备份脚本就不会被运行。

然而,如果你使用 anacron,你可以确保在你下次开启桌面/笔记本电脑的时候,备份脚本会被执行。

anacron 如何在 Linux 工作

anacron 任务被列在 /etc/anacrontab 中,任务可以使用下面的格式(anacron 文件中的注释必须以 # 号开始)安排。

period   delay   job-identifier   command

从上面的格式中:

  • period - 这是任务的频率,以天来指定,或者是 @daily@weekly@monthly 代表每天、每周、每月一次。你也可以使用数字:1 - 每天、7 - 每周、30 - 每月,或者 N - 几天。
  • delay - 这是在执行一个任务前等待的分钟数。
  • job-id - 这是写在日志文件中任务的独特名字。
  • command - 这是要执行的命令或 shell 脚本。

要浏览示例文件,输入:

$ ls -l /var/spool/anacron/
total 12
-rw------- 1 root root 9 Jun  1 10:25 cron.daily
-rw------- 1 root root 9 May 27 11:01 cron.monthly
-rw------- 1 root root 9 May 30 10:28 cron.weekly

这是实际发生的:

  • anacron 会检查任务是否已经在 period 字段指定的时间被被执行了。如果没有,则在等待 delay 字段中指定的分钟数后,执行 command 字段中指定的命令。
  • 一旦任务被执行了,它会使用 job-id(时间戳文件名)字段中指定的名称将日期记录在 /var/spool/anacron 目录中的时间戳文件中。

现在让我们看一个例子。这个会每天运行 /home/aaronkilik/bin/backup.sh 脚本:

@daily    10    example.daily   /bin/bash /home/aaronkilik/bin/backup.sh

当机器在 backup.sh 期望被运行时是关机的,anacron 会在机器开机十分钟之后运行它,而不用再等待 7 天。

这里有两个你应该理解的 anacrontab 文件的重要变量:

  • START_HOURS_RANGE - 这个设置任务开始运行的时间范围(也就是任务只在这几个小时内运行)。
  • RANDOM_DELAY - 这定义添加到用户定义的任务延迟的最大随机延迟(默认为 45)。

这是你的 anacrontab 文件可能看上去的样子。

Anacron – /etc/anacrontab

# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
HOME=/root
LOGNAME=root
# These replace cron's entries
1       5       cron.daily      run-parts --report /etc/cron.daily
7       10      cron.weekly     run-parts --report /etc/cron.weekly
@monthly        15      cron.monthly    run-parts --report /etc/cron.monthly
@daily    10    example.daily   /bin/bash /home/aaronkilik/bin/backup.sh                                                                      

下面是 cron 以及 anacron 的比较,帮助你理解何时用他们其中一个。

cronanacron
它是守护进程它不是守护进程
适合服务器适合桌面/笔记本电脑
可以让你以分钟级运行计划任务只能让你以天为基础来运行计划任务
关机时不会执行计划任务如果计划任务到期,机器是关机的,那么它会在机器下次开机后执行计划任务
普通用户和 root 用户都可以使用只有 root 用户可以使用(使用特定的配置启动普通任务)

cron 和 anacron 主要的区别在于 cron 能在那些持续运行的机器上有效地运行,而 anacron 是针对那些会在一天内或者一周内会关机的机器。

如果你还知道其他方式,请在评论栏中与我们分享。

(题图:Pixabay,CC0)


作者简介:

Aaron Kili 是一个 Linux 和 F.O.S.S 爱好者、Linux 系统管理员、网络开发人员,现在也是 TecMint 的内容创作者,他喜欢和电脑一起工作,坚信共享知识。


via: https://www.tecmint.com/cron-vs-anacron-schedule-jobs-using-anacron-on-linux/

作者:Aaron Kili 译者:geekpi 校对:wxy

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

Linux 中的 comm 命令可以让用户按行比较两个已经排序好的文件。在本教程中,我们将使用一些浅显易懂的例子来讨论这个命令行工具。在开始之前,请注意,本教程中提到的所有例子都已经在 Ubuntu 16.04LTS 版本中测试过。

下面的例子将会告诉你 comm 命令是如何工作的。

1、 如何使用 comm 比较两个排序好的文件

要使用 comm 命令比较两个排序好的文件,只需要把它们的名字作为 comm 命令的参数。下面是通常的语法:

comm [name-of-first-file] [name-of-second-file]

比如,假设 file1file2 是这种情况下的两个文件。前者包含下面几行内容:

001
056
127
258

而后者包含下面几行内容:

002
056
167
369

此时,comm 命令的输出如下图所示:

comm file1 file2

你可以看到,输出包含 3 列。第一列是仅包含在 file1 中的内容,第二列是仅包含在 file2 中的内容,最后,第三列是两个文件中均包含的内容。

2、 如何不输出 comm 命令输出中的某些列

如果你想,你可以不输出 comm 命令输出中的某些列。对于该特性,你有三个命令行选项可用:-1-2-3 。正如你所猜想的,这些数字表示你不想输出的列。

比如,下面这个命令将会不输出上面例子中的第三列:

comm -3 file1 file2

因此,你可以看到,第三列并没有输出。

注意,你可以通过一个单一命令同时不输出多列内容。比如:

comm -12 file1 file2

上面这个命令将会不输出第一、二列。

3、 如何使用 comm 命令比较两个未排序好的文件

正如我们所知道的,comm 只可用于排序好的文件。如果发现其中一个文件未排序好,那么便会在输出中产生一条信息来告诉用户。比如,我们交换 file1 的第一行和第二行,然后与 file2 进行比较。下面是该命令的输出:

你可以看到,这个命令产生一个输出告诉我们:file1 还没有排序好。此时,如果你不想让这个工具检查输入是否已经排序好,那么你可以使用 --nocheck-order 选项:

comm --nocheck-order file1 file2

你可以看到,前面出现的提示信息已经消失了。

注意,如果你想明确告诉 comm 命令来检查输入文件是否排序好,那么你可以使用 --check-order 选项。

4、 如何用自定义字符串分隔 comm 命令的输出列

默认情况下,comm 命令的输出列之间是以空格分隔的。然而,如何你想使用一个自定义字符串作为分隔符,那么你可以使用 --output-delimiter 选项。使用该选项时需要指定你想用来作为分隔符的字符串。

comm --output-delimiter=+ file1 file2

比如,我们使用加号来作为分隔符:

5、 如何使 comm 的输出行以 NUL 字符终止

默认情况下,comm 命令的输出行以新行终止。然而,如果你想,那么你可以改为以 NUL 字符终止,只需要使用 -z 选项即可:

comm -z file1 file2

结论

comm 命令并没有特别多的特性性,我们在这儿已经讨论了它的绝大多数命令行选项。只需要理解和练习在这篇教程中讨论的内容,那么你便可以在日常工作中知道如何使用这个工具了。如果你有任何问题或者疑问,请前往该命令的 man 手册,或者在下面评论。


via: https://www.howtoforge.com/linux-comm-command/

作者:Himanshu Arora 译者:ucasFL 校对:wxy

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