分类 技术 下的文章

介绍

我不会告诉你怎么在自己的电脑上去构建、安装一个定制化的 Linux 内核,这样的资料太多了,它们会对你有帮助。本文会告诉你当你在内核源码路径里敲下make 时会发生什么。

当我刚刚开始学习内核代码时,Makefile 是我打开的第一个文件,这个文件看起来真令人害怕 :)。那时候这个 Makefile 还只包含了1591 行代码,当我开始写本文时,内核已经是4.2.0的第三个候选版本 了。

这个 makefile 是 Linux 内核代码的根 makefile ,内核构建就始于此处。是的,它的内容很多,但是如果你已经读过内核源代码,你就会发现每个包含代码的目录都有一个自己的 makefile。当然了,我们不会去描述每个代码文件是怎么编译链接的,所以我们将只会挑选一些通用的例子来说明问题。而你不会在这里找到构建内核的文档、如何整洁内核代码、tags 的生成和交叉编译 相关的说明,等等。我们将从make 开始,使用标准的内核配置文件,到生成了内核镜像 bzImage 结束。

如果你已经很了解 make 工具那是最好,但是我也会描述本文出现的相关代码。

让我们开始吧!

(题图来自:adafruit.com)

编译内核前的准备

在开始编译前要进行很多准备工作。最主要的就是找到并配置好配置文件,make 命令要使用到的参数都需要从这些配置文件获取。现在就让我们深入内核的根 makefile

内核的根 Makefile 负责构建两个主要的文件:vmlinux (内核镜像可执行文件)和模块文件。内核的 Makefile 从定义如下变量开始:

VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 0
EXTRAVERSION = -rc3
NAME = Hurr durr I'ma sheep

这些变量决定了当前内核的版本,并且被使用在很多不同的地方,比如同一个 Makefile 中的 KERNELVERSION

KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

接下来我们会看到很多ifeq 条件判断语句,它们负责检查传递给 make 的参数。内核的 Makefile 提供了一个特殊的编译选项 make help ,这个选项可以生成所有的可用目标和一些能传给 make 的有效的命令行参数。举个例子,make V=1 会在构建过程中输出详细的编译信息,第一个 ifeq 就是检查传递给 make 的 V=n 选项。

ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

export quiet Q KBUILD_VERBOSE

如果 V=n 这个选项传给了 make ,系统就会给变量 KBUILD_VERBOSE 选项附上 V 的值,否则的话KBUILD_VERBOSE 就会为 0。然后系统会检查 KBUILD_VERBOSE 的值,以此来决定 quietQ 的值。符号 @ 控制命令的输出,如果它被放在一个命令之前,这条命令的输出将会是 CC scripts/mod/empty.o,而不是Compiling .... scripts/mod/empty.o(LCTT 译注:CC 在 makefile 中一般都是编译命令)。在这段最后,系统导出了所有的变量。

下一个 ifeq 语句检查的是传递给 make 的选项 O=/dir,这个选项允许在指定的目录 dir 输出所有的结果文件:

ifeq ($(KBUILD_SRC),)

ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

ifneq ($(KBUILD_OUTPUT),)
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
                                && /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))

sub-make: FORCE
    $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
    -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))

skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

系统会检查变量 KBUILD_SRC,它代表内核代码的顶层目录,如果它是空的(第一次执行 makefile 时总是空的),我们会设置变量 KBUILD_OUTPUT 为传递给选项 O 的值(如果这个选项被传进来了)。下一步会检查变量 KBUILD_OUTPUT ,如果已经设置好,那么接下来会做以下几件事:

  • 将变量 KBUILD_OUTPUT 的值保存到临时变量 saved-output
  • 尝试创建给定的输出目录;
  • 检查创建的输出目录,如果失败了就打印错误;
  • 如果成功创建了输出目录,那么就在新目录重新执行 make 命令(参见选项-C)。

下一个 ifeq 语句会检查传递给 make 的选项 CM

ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

第一个选项 C 会告诉 makefile 需要使用环境变量 $CHECK 提供的工具来检查全部 c 代码,默认情况下会使用sparse。第二个选项 M 会用来编译外部模块(本文不做讨论)。

系统还会检查变量 KBUILD_SRC,如果 KBUILD_SRC 没有被设置,系统会设置变量 srctree.

ifeq ($(KBUILD_SRC),)
        srctree := .
endif

objtree := .
src     := $(srctree)
obj     := $(objtree)

export srctree objtree VPATH

这将会告诉 Makefile 内核的源码树就在执行 make 命令的目录,然后要设置 objtree 和其他变量为这个目录,并且将这些变量导出。接着就是要获取 SUBARCH 的值,这个变量代表了当前的系统架构(LCTT 译注:一般都指CPU 架构):

SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
                  -e s/sun4u/sparc64/ \
                  -e s/arm.*/arm/ -e s/sa110/arm/ \
                  -e s/s390x/s390/ -e s/parisc64/parisc/ \
                  -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
                  -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )

如你所见,系统执行 uname 得到机器、操作系统和架构的信息。因为我们得到的是 uname 的输出,所以我们需要做一些处理再赋给变量 SUBARCH 。获得 SUBARCH 之后就要设置SRCARCHhfr-archSRCARCH 提供了硬件架构相关代码的目录,hfr-arch 提供了相关头文件的目录:

ifeq ($(ARCH),i386)
        SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
        SRCARCH := x86
endif

hdr-arch  := $(SRCARCH)

注意:ARCHSUBARCH 的别名。如果没有设置过代表内核配置文件路径的变量 KCONFIG_CONFIG,下一步系统会设置它,默认情况下就是 .config

KCONFIG_CONFIG  ?= .config
export KCONFIG_CONFIG

以及编译内核过程中要用到的 shell

CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
      else if [ -x /bin/bash ]; then echo /bin/bash; \
      else echo sh; fi ; fi)

接下来就要设置一组和编译内核的编译器相关的变量。我们会设置主机的 CC++ 的编译器及相关配置项:

HOSTCC       = gcc
HOSTCXX      = g++
HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2

接下来会去适配代表编译器的变量 CC,那为什么还要 HOST* 这些变量呢?这是因为 CC 是编译内核过程中要使用的目标架构的编译器,但是 HOSTCC 是要被用来编译一组 host 程序的(下面我们就会看到)。

然后我们就看到变量 KBUILD_MODULESKBUILD_BUILTIN 的定义,这两个变量决定了我们要编译什么东西(内核、模块或者两者):

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

在这我们可以看到这些变量的定义,并且,如果们仅仅传递了 modulesmake,变量 KBUILD_BUILTIN 会依赖于内核配置选项 CONFIG_MODVERSIONS

下一步操作是引入下面的文件:

include scripts/Kbuild.include

文件 Kbuild 或者又叫做 Kernel Build System 是一个用来管理构建内核及其模块的特殊框架。kbuild 文件的语法与 makefile 一样。文件scripts/Kbuild.includekbuild 系统提供了一些常规的定义。因为我们包含了这个 kbuild 文件,我们可以看到和不同工具关联的这些变量的定义,这些工具会在内核和模块编译过程中被使用(比如链接器、编译器、来自 binutils 的二进制工具包 ,等等):

AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm
STRIP       = $(CROSS_COMPILE)strip
OBJCOPY     = $(CROSS_COMPILE)objcopy
OBJDUMP     = $(CROSS_COMPILE)objdump
AWK     = awk
...
...
...

在这些定义好的变量后面,我们又定义了两个变量:USERINCLUDELINUXINCLUDE。他们包含了头文件的路径(第一个是给用户用的,第二个是给内核用的):

USERINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include/uapi \
        -Iarch/$(hdr-arch)/include/generated/uapi \
        -I$(srctree)/include/uapi \
        -Iinclude/generated/uapi \
        -include $(srctree)/include/linux/kconfig.h

LINUXINCLUDE    := \
        -I$(srctree)/arch/$(hdr-arch)/include \
        ...

以及给 C 编译器的标准标志:

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89

这并不是最终确定的编译器标志,它们还可以在其他 makefile 里面更新(比如 arch/ 里面的 kbuild)。变量定义完之后,全部会被导出供其他 makefile 使用。

下面的两个变量 RCS_FIND_IGNORERCS_TAR_IGNORE 包含了被版本控制系统忽略的文件:

export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o    \
              -name CVS -o -name .pc -o -name .hg -o -name .git \) \
              -prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
             --exclude CVS --exclude .pc --exclude .hg --exclude .git

这就是全部了,我们已经完成了所有的准备工作,下一个点就是如果构建vmlinux

直面内核构建

现在我们已经完成了所有的准备工作,根 makefile(注:内核根目录下的 makefile)的下一步工作就是和编译内核相关的了。在这之前,我们不会在终端看到 make 命令输出的任何东西。但是现在编译的第一步开始了,这里我们需要从内核根 makefile 的 598 行开始,这里可以看到目标vmlinux

all: vmlinux
    include arch/$(SRCARCH)/Makefile

不要操心我们略过的从 export RCS_FIND_IGNORE.....all: vmlinux..... 这一部分 makefile 代码,他们只是负责根据各种配置文件(make *.config)生成不同目标内核的,因为之前我就说了这一部分我们只讨论构建内核的通用途径。

目标 all: 是在命令行如果不指定具体目标时默认使用的目标。你可以看到这里包含了架构相关的 makefile(在这里就指的是 arch/x86/Makefile)。从这一时刻起,我们会从这个 makefile 继续进行下去。如我们所见,目标 all 依赖于根 makefile 后面声明的 vmlinux

vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE

vmlinux 是 linux 内核的静态链接可执行文件格式。脚本 scripts/link-vmlinux.sh 把不同的编译好的子模块链接到一起形成了 vmlinux。

第二个目标是 vmlinux-deps,它的定义如下:

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

它是由内核代码下的每个顶级目录的 built-in.o 组成的。之后我们还会检查内核所有的目录,kbuild 会编译各个目录下所有的对应 $(obj-y) 的源文件。接着调用 $(LD) -r 把这些文件合并到一个 build-in.o 文件里。此时我们还没有vmlinux-deps,所以目标 vmlinux 现在还不会被构建。对我而言 vmlinux-deps 包含下面的文件:

arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o
arch/x86/kernel/head64.o    arch/x86/kernel/head.o
init/built-in.o             usr/built-in.o
arch/x86/built-in.o         kernel/built-in.o
mm/built-in.o               fs/built-in.o
ipc/built-in.o              security/built-in.o
crypto/built-in.o           block/built-in.o
lib/lib.a                   arch/x86/lib/lib.a
lib/built-in.o              arch/x86/lib/built-in.o
drivers/built-in.o          sound/built-in.o
firmware/built-in.o         arch/x86/pci/built-in.o
arch/x86/power/built-in.o   arch/x86/video/built-in.o
net/built-in.o

下一个可以被执行的目标如下:

$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

就像我们看到的,vmlinux-dir 依赖于两部分:preparescripts。第一个 prepare 定义在内核的根 makefile 中,准备工作分成三个阶段:

prepare: prepare0
prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.
archprepare: archheaders archscripts prepare1 scripts_basic

prepare1: prepare2 $(version_h) include/generated/utsrelease.h \
                   include/config/auto.conf
    $(cmd_crmodverdir)
prepare2: prepare3 outputmakefile asm-generic

第一个 prepare0 展开到 archprepare ,后者又展开到 archheaderarchscripts,这两个变量定义在 x86_64 相关的 Makefile。让我们看看这个文件。x86_64 特定的 makefile 从变量定义开始,这些变量都是和特定架构的配置文件 (defconfig,等等)有关联。在定义了编译 16-bit 代码的编译选项之后,根据变量 BITS 的值,如果是 32, 汇编代码、链接器、以及其它很多东西(全部的定义都可以在arch/x86/Makefile找到)对应的参数就是 i386,而 64 就对应的是 x86_84

第一个目标是 makefile 生成的系统调用列表(syscall table)中的 archheaders

archheaders:
    $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all

第二个目标是 makefile 里的 archscripts

archscripts: scripts_basic
    $(Q)$(MAKE) $(build)=arch/x86/tools relocs

我们可以看到 archscripts 是依赖于根 Makefile里的scripts_basic 。首先我们可以看出 scripts_basic 是按照 scripts/basic 的 makefile 执行 make 的:

scripts_basic:
    $(Q)$(MAKE) $(build)=scripts/basic

scripts/basic/Makefile 包含了编译两个主机程序 fixdepbin2 的目标:

hostprogs-y := fixdep
hostprogs-$(CONFIG_BUILD_BIN2C)     += bin2c
always      := $(hostprogs-y)

$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep

第一个工具是 fixdep:用来优化 gcc 生成的依赖列表,然后在重新编译源文件的时候告诉make。第二个工具是 bin2c,它依赖于内核配置选项 CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口(LCTT 译注:即 stdin)收到的二进制流通过标准输出接口(即:stdout)转换成 C 头文件的非常小的 C 程序。你可能注意到这里有些奇怪的标志,如 hostprogs-y 等。这个标志用于所有的 kbuild 文件,更多的信息你可以从documentation 获得。在我们这里, hostprogs-y 告诉 kbuild 这里有个名为 fixed 的程序,这个程序会通过和 Makefile 相同目录的 fixdep.c 编译而来。

执行 make 之后,终端的第一个输出就是 kbuild 的结果:

$ make
  HOSTCC  scripts/basic/fixdep

当目标 script_basic 被执行,目标 archscripts 就会 make arch/x86/tools 下的 makefile 和目标 relocs:

$(Q)$(MAKE) $(build)=arch/x86/tools relocs

包含了重定位 的信息的代码 relocs_32.crelocs_64.c 将会被编译,这可以在make 的输出中看到:

  HOSTCC  arch/x86/tools/relocs_32.o
  HOSTCC  arch/x86/tools/relocs_64.o
  HOSTCC  arch/x86/tools/relocs_common.o
  HOSTLD  arch/x86/tools/relocs

在编译完 relocs.c 之后会检查 version.h:

$(version_h): $(srctree)/Makefile FORCE
    $(call filechk,version.h)
    $(Q)rm -f $(old_version_h)

我们可以在输出看到它:

CHK     include/config/kernel.release

以及在内核的根 Makefiel 使用 arch/x86/include/generated/asm 的目标 asm-generic 来构建 generic 汇编头文件。在目标 asm-generic 之后,archprepare 就完成了,所以目标 prepare0 会接着被执行,如我上面所写:

prepare0: archprepare FORCE
    $(Q)$(MAKE) $(build)=.

注意 build,它是定义在文件 scripts/Kbuild.include,内容是这样的:

build := -f $(srctree)/scripts/Makefile.build obj

或者在我们的例子中,它就是当前源码目录路径:.

$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=.

脚本 scripts/Makefile.build 通过参数 obj 给定的目录找到 Kbuild 文件,然后引入 kbuild 文件:

include $(kbuild-file)

并根据这个构建目标。我们这里 . 包含了生成 kernel/bounds.sarch/x86/kernel/asm-offsets.sKbuild 文件。在此之后,目标 prepare 就完成了它的工作。 vmlinux-dirs 也依赖于第二个目标 scripts ,它会编译接下来的几个程序:filealiasmk_elfconfigmodpost 等等。之后,scripts/host-programs 就可以开始编译我们的目标 vmlinux-dirs 了。

首先,我们先来理解一下 vmlinux-dirs 都包含了那些东西。在我们的例子中它包含了下列内核目录的路径:

init usr arch/x86 kernel mm fs ipc security crypto block
drivers sound firmware arch/x86/pci arch/x86/power
arch/x86/video net lib arch/x86/lib

我们可以在内核的根 Makefile 里找到 vmlinux-dirs 的定义:

vmlinux-dirs    := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
             $(core-y) $(core-m) $(drivers-y) $(drivers-m) \
             $(net-y) $(net-m) $(libs-y) $(libs-m)))

init-y      := init/
drivers-y   := drivers/ sound/ firmware/
net-y       := net/
libs-y      := lib/
...
...
...

这里我们借助函数 patsubstfilter去掉了每个目录路径里的符号 /,并且把结果放到 vmlinux-dirs 里。所以我们就有了 vmlinux-dirs 里的目录列表,以及下面的代码:

$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

符号 $@ 在这里代表了 vmlinux-dirs,这就表明程序会递归遍历从 vmlinux-dirs 以及它内部的全部目录(依赖于配置),并且在对应的目录下执行 make 命令。我们可以在输出看到结果:

  CC      init/main.o
  CHK     include/generated/compile.h
  CC      init/version.o
  CC      init/do_mounts.o
  ...
  CC      arch/x86/crypto/glue_helper.o
  AS      arch/x86/crypto/aes-x86_64-asm_64.o
  CC      arch/x86/crypto/aes_glue.o
  ...
  AS      arch/x86/entry/entry_64.o
  AS      arch/x86/entry/thunk_64.o
  CC      arch/x86/entry/syscall_64.o

每个目录下的源代码将会被编译并且链接到 built-io.o 里:

$ find . -name built-in.o
./arch/x86/crypto/built-in.o
./arch/x86/crypto/sha-mb/built-in.o
./arch/x86/net/built-in.o
./init/built-in.o
./usr/built-in.o
...
...

好了,所有的 built-in.o 都构建完了,现在我们回到目标 vmlinux 上。你应该还记得,目标 vmlinux 是在内核的根makefile 里。在链接 vmlinux 之前,系统会构建 samples, Documentation 等等,但是如上文所述,我不会在本文描述这些。

vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
    ...
    ...
    +$(call if_changed,link-vmlinux)

你可以看到,调用脚本 scripts/link-vmlinux.sh 的主要目的是把所有的 built-in.o 链接成一个静态可执行文件,和生成 System.map。 最后我们来看看下面的输出:

  LINK    vmlinux
  LD      vmlinux.o
  MODPOST vmlinux.o
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  LD      init/built-in.o
  KSYM    .tmp_kallsyms1.o
  KSYM    .tmp_kallsyms2.o
  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map

vmlinuxSystem.map 生成在内核源码树根目录下。

$ ls vmlinux System.map 
System.map  vmlinux

这就是全部了,vmlinux 构建好了,下一步就是创建 bzImage.

制作bzImage

bzImage 就是压缩了的 linux 内核镜像。我们可以在构建了 vmlinux 之后通过执行 make bzImage 获得bzImage。同时我们可以仅仅执行 make 而不带任何参数也可以生成 bzImage ,因为它是在 arch/x86/kernel/Makefile 里预定义的、默认生成的镜像:

all: bzImage

让我们看看这个目标,它能帮助我们理解这个镜像是怎么构建的。我已经说过了 bzImage 是被定义在 arch/x86/kernel/Makefile,定义如下:

bzImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
    $(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
    $(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@

在这里我们可以看到第一次为 boot 目录执行 make,在我们的例子里是这样的:

boot := arch/x86/boot

现在的主要目标是编译目录 arch/x86/bootarch/x86/boot/compressed 的代码,构建 setup.binvmlinux.bin,最后用这两个文件生成 bzImage。第一个目标是定义在 arch/x86/boot/Makefile$(obj)/setup.elf:

$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
    $(call if_changed,ld)

我们已经在目录 arch/x86/boot 有了链接脚本 setup.ld,和扩展到 boot 目录下全部源代码的变量 SETUP_OBJS 。我们可以看看第一个输出:

  AS      arch/x86/boot/bioscall.o
  CC      arch/x86/boot/cmdline.o
  AS      arch/x86/boot/copy.o
  HOSTCC  arch/x86/boot/mkcpustr
  CPUSTR  arch/x86/boot/cpustr.h
  CC      arch/x86/boot/cpu.o
  CC      arch/x86/boot/cpuflags.o
  CC      arch/x86/boot/cpucheck.o
  CC      arch/x86/boot/early_serial_console.o
  CC      arch/x86/boot/edd.o

下一个源码文件是 arch/x86/boot/header.S,但是我们不能现在就编译它,因为这个目标依赖于下面两个头文件:

$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h

第一个头文件 voffset.h 是使用 sed 脚本生成的,包含用 nm 工具从 vmlinux 获取的两个地址:

#define VO__end 0xffffffff82ab0000
#define VO__text 0xffffffff81000000

这两个地址是内核的起始和结束地址。第二个头文件 zoffset.harch/x86/boot/compressed/Makefile 可以看出是依赖于目标 vmlinux的:

$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,zoffset)

目标 $(obj)/compressed/vmlinux 依赖于 vmlinux-objs-y —— 说明需要编译目录 arch/x86/boot/compressed 下的源代码,然后生成 vmlinux.binvmlinux.bin.bz2,和编译工具 mkpiggy。我们可以在下面的输出看出来:

  LDS     arch/x86/boot/compressed/vmlinux.lds
  AS      arch/x86/boot/compressed/head_64.o
  CC      arch/x86/boot/compressed/misc.o
  CC      arch/x86/boot/compressed/string.o
  CC      arch/x86/boot/compressed/cmdline.o
  OBJCOPY arch/x86/boot/compressed/vmlinux.bin
  BZIP2   arch/x86/boot/compressed/vmlinux.bin.bz2
  HOSTCC  arch/x86/boot/compressed/mkpiggy

vmlinux.bin 是去掉了调试信息和注释的 vmlinux 二进制文件,加上了占用了 u32 (LCTT 译注:即4-Byte)的长度信息的 vmlinux.bin.all 压缩后就是 vmlinux.bin.bz2。其中 vmlinux.bin.all 包含了 vmlinux.binvmlinux.relocs(LCTT 译注:vmlinux 的重定位信息),其中 vmlinux.relocsvmlinux 经过程序 relocs 处理之后的 vmlinux 镜像(见上文所述)。我们现在已经获取到了这些文件,汇编文件 piggy.S 将会被 mkpiggy 生成、然后编译:

  MKPIGGY arch/x86/boot/compressed/piggy.S
  AS      arch/x86/boot/compressed/piggy.o

这个汇编文件会包含经过计算得来的、压缩内核的偏移信息。处理完这个汇编文件,我们就可以看到 zoffset 生成了:

  ZOFFSET arch/x86/boot/zoffset.h

现在 zoffset.hvoffset.h 已经生成了,arch/x86/boot 里的源文件可以继续编译:

  AS      arch/x86/boot/header.o
  CC      arch/x86/boot/main.o
  CC      arch/x86/boot/mca.o
  CC      arch/x86/boot/memory.o
  CC      arch/x86/boot/pm.o
  AS      arch/x86/boot/pmjump.o
  CC      arch/x86/boot/printf.o
  CC      arch/x86/boot/regs.o
  CC      arch/x86/boot/string.o
  CC      arch/x86/boot/tty.o
  CC      arch/x86/boot/video.o
  CC      arch/x86/boot/video-mode.o
  CC      arch/x86/boot/video-vga.o
  CC      arch/x86/boot/video-vesa.o
  CC      arch/x86/boot/video-bios.o

所有的源代码会被编译,他们最终会被链接到 setup.elf

  LD      arch/x86/boot/setup.elf

或者:

ld -m elf_x86_64   -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/mca.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf

最后的两件事是创建包含目录 arch/x86/boot/* 下的编译过的代码的 setup.bin

objcopy  -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin

以及从 vmlinux 生成 vmlinux.bin :

objcopy  -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin

最最后,我们编译主机程序 arch/x86/boot/tools/build.c,它将会用来把 setup.binvmlinux.bin 打包成 bzImage:

arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage

实际上 bzImage 就是把 setup.binvmlinux.bin 连接到一起。最终我们会看到输出结果,就和那些用源码编译过内核的同行的结果一样:

Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready  (#5)

全部结束。

结论

这就是本文的结尾部分。本文我们了解了编译内核的全部步骤:从执行 make 命令开始,到最后生成 bzImage。我知道,linux 内核的 makefile 和构建 linux 的过程第一眼看起来可能比较迷惑,但是这并不是很难。希望本文可以帮助你理解构建 linux 内核的整个流程。

链接


via: https://github.com/0xAX/linux-insides/blob/master/Misc/how_kernel_compiled.md

译者:oska874 校对:wxy

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

问题: 当我试图在 Ubuntu 上的 Wireshark 中打开一个 pre-recorded 数据包转储时,它的界面突然死机,在我运行 Wireshark 的终端出现了下面的错误和警告。我该如何解决这个问题?
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GObject'
(wireshark:3480): GLib-GObject-CRITICAL **: g_object_set_qdata_full: assertion 'G_IS_OBJECT (object)' failed
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GtkRange'
(wireshark:3480): Gtk-CRITICAL **: gtk_range_get_adjustment: assertion 'GTK_IS_RANGE (range)' failed
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GtkOrientable'
(wireshark:3480): Gtk-CRITICAL **: gtk_orientable_get_orientation: assertion 'GTK_IS_ORIENTABLE (orientable)' failed
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GtkScrollbar'
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GtkWidget'
(wireshark:3480): GLib-GObject-WARNING **: invalid unclassed pointer in cast to 'GObject'
(wireshark:3480): GLib-GObject-CRITICAL **: g_object_get_qdata: assertion 'G_IS_OBJECT (object)' failed
(wireshark:3480): Gtk-CRITICAL **: gtk_widget_set_name: assertion 'GTK_IS_WIDGET (widget)' failed

Wireshark 是一个基于 GUI 的数据包捕获和嗅探工具。该工具被网络管理员普遍使用,网络安全工程师或开发人员对于各种任务的数据包级的网络分析是必需的,例如在网络故障,漏洞测试,应用程序调试,或逆向协议工程是必需的。 Wireshark 允许实时记录数据包,并通过便捷的图形用户界面浏览他们的协议首部和有效负荷。

这是 Wireshark 的 UI,尤其是在 Ubuntu 桌面下运行时,当你向上或向下滚动分组列表视图时,或开始加载一个 pre-recorded 包转储文件时,有时会挂起或冻结,并出现以下错误。

显然,这个错误是由 Wireshark 和叠加滚动条之间的一些不兼容造成的,在最新的 Ubuntu 桌面还没有被解决(例如,Ubuntu 15.04 的桌面)。

一种避免 Wireshark 的 UI 卡死的办法就是 暂时禁用叠加滚动条。在 Wireshark 上有两种方法来禁用叠加滚动条,这取决于你在桌面上如何启动 Wireshark 的。

命令行解决方法

叠加滚动条可以通过设置"LIBOVERLAY\_SCROLLBAR"环境变量为“0”来被禁止。

所以,如果你是在终端使用命令行启动 Wireshark 的,你可以在 Wireshark 中禁用叠加滚动条,如下所示。

打开你的 .bashrc 文件,并定义以下 alias。

alias wireshark="LIBOVERLAY_SCROLLBAR=0 /usr/bin/wireshark"

桌面启动解决方法

如果你是使用桌面启动器启动的 Wireshark,你可以编辑它的桌面启动器文件。

$ sudo vi /usr/share/applications/wireshark.desktop

查找以"Exec"开头的行,并如下更改。

Exec=env LIBOVERLAY_SCROLLBAR=0 wireshark %f

虽然这种解决方法可以在系统级帮助到所有桌面用户,但升级 Wireshark 后被覆盖就没用了。如果你想保留修改的 .desktop 文件,如下所示将它复制到你的主目录。

$ cp /usr/share/applications/wireshark.desktop ~/.local/share/applications/ 

via: http://ask.xmodulo.com/fix-wireshark-gui-freeze-linux-desktop.html

作者:Dan Nanni 译者:strugglingyouth 校对:wxy

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

和管理其它Linux服务器一样,管理一个 RHEL 7 服务器要求你能够添加、修改、暂停或删除用户帐户,并且授予他们执行其分配的任务所需的文件、目录、其它系统资源所必要的权限。

User and Group Management in Linux

RHCSA: 用户和组管理 – Part 3

管理用户帐户

如果想要给RHEL 7 服务器添加账户,你需要以root用户执行如下两条命令之一:

# adduser [new_account]
# useradd [new_account]

当添加新的用户帐户时,默认会执行下列操作。

  • 它/她的主目录就会被创建(一般是"/home/用户名",除非你特别设置)
  • 一些隐藏文件 如.bash_logout, .bash_profile 以及 .bashrc 会被复制到用户的主目录,它们会为用户的回话提供环境变量。你可以进一步查看它们的相关细节。
  • 会为您的账号添加一个邮件池目录。
  • 会创建一个和用户名同样的组(LCTT 译注:除非你给新创建的用户指定了组)。

用户帐户的全部信息被保存在/etc/passwd文件。这个文件以如下格式保存了每一个系统帐户的所有信息(字段以“:”分割)

[username]:[x]:[UID]:[GID]:[Comment]:[Home directory]:[Default shell]
  • [username][Comment] 其意自明,就是用户名和备注
  • 第二个‘x’表示帐户的启用了密码保护(记录在/etc/shadow文件),密码用于登录[username]
  • [UID][GID]是整数,它们表明了[username]的用户ID 和所属的主组ID

最后。

  • [Home directory]显示[username]的主目录的绝对路径
  • [Default shell] 是当用户登录系统后使用的默认shell

另外一个你必须要熟悉的重要的文件是存储组信息的/etc/group。和/etc/passwd类似,也是每行一个记录,字段由“:”分割

[Group name]:[Group password]:[GID]:[Group members]
  • [Group name] 是组名
  • 这个组是否使用了密码 (如果是"x"意味着没有)
  • [GID]: 和/etc/passwd中一样
  • [Group members]:用户列表,使用“,”隔开。里面包含组内的所有用户

添加过帐户后,任何时候你都可以通过 usermod 命令来修改用户账户信息,基本的语法如下:

# usermod [options] [username]

相关阅读

示例1 : 设置帐户的过期时间

如果你的公司有一些短期使用的帐户或者你要在有限时间内授予访问,你可以使用 --expiredate 参数 ,后加YYYY-MM-DD 格式的日期。为了查看是否生效,你可以使用如下命令查看

# chage -l [username]

帐户更新前后的变动如下图所示

Change User Account Information

修改用户信息

示例 2: 向组内追加用户

除了创建用户时的主用户组,一个用户还能被添加到别的组。你需要使用 -aG或 -append -group 选项,后跟逗号分隔的组名。

示例 3: 修改用户主目录或默认Shell

如果因为一些原因,你需要修改默认的用户主目录(一般为 /home/用户名),你需要使用 -d 或 -home 参数,后跟绝对路径来修改主目录。

如果有用户想要使用其它的shell来取代默认的bash(比如zsh)。使用 usermod ,并使用 -shell 的参数,后加新的shell的路径。

示例 4: 展示组内的用户

当把用户添加到组中后,你可以使用如下命令验证属于哪一个组

# groups [username]
# id [username]

下面图片的演示了示例2到示例4

Adding User to Supplementary Group

添加用户到额外的组

在上面的示例中:

# usermod --append --groups gacanepa,users --home /tmp --shell /bin/sh tecmint

如果想要从组内删除用户,取消 --append 选项,并使用 --groups 和你要用户属于的组的列表。

示例 5: 通过锁定密码来停用帐户

如果想要关闭帐户,你可以使用 -l(小写的L)或 -lock 选项来锁定用户的密码。这将会阻止用户登录。

示例 6: 解锁密码

当你想要重新启用帐户让它可以继续登录时,使用 -u 或 –unlock 选项来解锁用户的密码,就像示例5 介绍的那样

# usermod --unlock tecmint

下面的图片展示了示例5和示例6:

Lock Unlock User Account

锁定上锁用户

示例 7:删除组和用户

如果要删除一个组,你需要使用 groupdel ,如果需要删除用户 你需要使用 userdel (添加 -r 可以删除主目录和邮件池的内容)。

# groupdel [group_name]        # 删除组
# userdel -r [user_name]       # 删除用户,并删除主目录和邮件池

如果一些文件属于该组,删除组时它们不会也被删除。但是组拥有者的名字将会被设置为删除掉的组的GID。

列举,设置,并且修改标准 ugo/rwx 权限

著名的 ls 命令 是管理员最好的助手. 当我们使用 -l 参数, 这个工具允许您以长格式(或详细格式)查看一个目录中的内容。

而且,该命令还可以用于单个文件中。无论哪种方式,在“ls”输出中的前10个字符表示每个文件的属性。

这10个字符序列的第一个字符用于表示文件类型:

  • – (连字符): 一个标准文件
  • d: 一个目录
  • l: 一个符号链接
  • c: 字符设备(将数据作为字节流,例如终端)
  • b: 块设备(以块的方式处理数据,例如存储设备)

文件属性的接下来的九个字符,分为三个组,被称为文件模式,并注明读(r)、写(w)、和执行(x)权限授予文件的所有者、文件的所有组、和其它的用户(通常被称为“世界”)。

同文件上的读取权限允许文件被打开和读取一样,如果目录同时有执行权限时,就允许其目录内容被列出。此外,如果一个文件有执行权限,就允许它作为一个程序运行。

文件权限是通过chmod命令改变的,它的基本语法如下:

# chmod [new_mode] file

new\_mode 是一个八进制数或表达式,用于指定新的权限。随意试试各种权限看看是什么效果。或者您已经有了一个更好的方式来设置文件的权限,你也可以用你自己的方式自由地试试。

八进制数可以基于二进制等价计算,可以从所需的文件权限的文件的所有者、所有组、和世界组合成。每种权限都等于2的幂(R = 2^2,W = 2^1,x = 2^0),没有时即为0。例如:

File Permissions

文件权限

在八进制形式下设置文件的权限,如上图所示

# chmod 744 myfile

请用马上来对比一下我们以前的计算,在更改文件的权限后,我们的实际输出为:

Long List Format

长列表格式

示例 8: 寻找777权限的文件

出于安全考虑,你应该确保在正常情况下,尽可能避免777权限(任何人可读、可写、可执行的文件)。虽然我们会在以后的教程中教你如何更有效地找到您的系统的具有特定权限的全部文件,你现在仍可以组合使用ls 和 grep来获取这种信息。

在下面的例子,我们会寻找 /etc 目录下的777权限文件。注意,我们要使用第二章:文件和目录管理中讲到的管道的知识:

# ls -l /etc | grep rwxrwxrwx

Find All Files with 777 Permission

查找所有777权限的文件

示例 9: 为所有用户指定特定权限

shell脚本,以及一些二进制文件,所有用户都应该有权访问(不只是其相应的所有者和组),应该有相应的执行权限(我们会讨论特殊情况下的问题):

# chmod a+x script.sh

注意: 我们可以使用表达式设置文件模式,表示用户权限的字母如“u”,组所有者权限的字母“g”,其余的为“o” ,同时具有所有权限为“a”。权限可以通过+- 来授予和收回。

Set Execute Permission on File

为文件设置执行权限

长目录列表还用两列显示了该文件的所有者和所有组。此功能可作为系统中文件的第一级访问控制方法:

Check File Owner and Group

检查文件的所有者和所有组

改变文件的所有者,您应该使用chown命令。请注意,您可以在同时或分别更改文件的所有组:

# chown user:group file

你可以更改用户或组,或在同时更改两个属性,但是不要忘记冒号区分,如果你想要更新其它属性,让另外的部分为空:

# chown :group file              # 仅改变所有组
# chown user: file               # 仅改变所有者

示例 10:从一个文件复制权限到另一个文件

如果你想“克隆”一个文件的所有权到另一个,你可以这样做,使用–reference参数,如下:

# chown --reference=ref_file file

ref\_file的所有信息会复制给 file

Clone File Ownership

复制文件属主信息

设置 SETGID 协作目录

假如你需要授予在一个特定的目录中拥有访问所有的文件的权限给一个特定的用户组,你有可能需要使用给目录设置setgid的方法。当setgid设置后,该真实用户的有效GID会变成属主的GID。

因此,任何访问该文件的用户会被授予该文件的属组的权限。此外,当setgid设置在一个目录中,新创建的文件继承组该目录的组,而且新创建的子目录也将继承父目录的setgid权限。

# chmod g+s [filename]

要以八进制形式设置 setgid,需要在基本权限前缀以2。

# chmod 2755 [directory]

总结

扎实的用户和组管理知识,以及标准和特殊的 Linux权限管理,通过实践,可以帮你快速解决 RHEL 7 服务器的文件权限问题。

我向你保证,当你按照本文所概述的步骤和使用系统文档(在本系列的第一章 回顾基础命令及系统文档中讲到), 你将掌握基本的系统管理的能力。

请随时使用下面的评论框让我们知道你是否有任何问题或意见。


via: http://www.tecmint.com/rhcsa-exam-manage-users-and-groups/

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

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

我成功地在 Ubuntu 15.04 下用 Gnome Network Manager 创建了一个无线AP热点。接下来我要分享一下我的步骤。请注意:你必须要有一个可以用来创建AP热点的无线网卡。如果你不知道如何确认它的话,在终端(Terminal)里输入iw list

如果你没有安装iw的话, 在Ubuntu下你可以使用sudo apt-get install iw进行安装.

在你键入iw list之后, 查看“支持的接口模式”, 你应该会看到类似下面的条目中看到 AP:

Supported interface modes:

* IBSS
* managed
* AP
* AP/VLAN
* monitor
* mesh point

让我们一步步看:

1、 断开WIFI连接。使用有线网络接入你的笔记本。

2、 在顶栏面板里点击网络的图标 -> Edit Connections(编辑连接) -> 在弹出窗口里点击Add(新增)按钮。

3、 在下拉菜单内选择Wi-Fi。

4、 接下来:

a、 输入一个链接名 比如: Hotspot 1

b、 输入一个 SSID 比如: Hotspot 1

c、 选择模式(mode): Infrastructure (基础设施)

d、 设备 MAC 地址: 在下拉菜单里选择你的无线设备

5、 进入Wi-Fi安全选项卡,选择 WPA & WPA2 Personal 并且输入密码。 6、 进入IPv4设置选项卡,在Method(方法)下拉菜单里,选择Shared to other computers(共享至其他电脑)。

7、 进入IPv6选项卡,在Method(方法)里设置为忽略ignore (只有在你不使用IPv6的情况下这么做) 8、 点击 Save(保存) 按钮以保存配置。 9、 从 menu/dash 里打开Terminal。 10、 修改你刚刚使用 network settings 创建的连接。

使用 VIM 编辑器:

sudo vim /etc/NetworkManager/system-connections/Hotspot

或使用Gedit 编辑器:

gksu gedit /etc/NetworkManager/system-connections/Hotspot

把名字 Hotspot 用你在第4步里起的连接名替换掉。

a、 把 mode=infrastructure 改成 mode=ap 并且保存文件。 b、 一旦你保存了这个文件,你应该能在 Wifi 菜单里看到你刚刚建立的AP了。(如果没有的话请再顶栏里 关闭/打开 Wifi 选项一次)

11、你现在可以把你的设备连上Wifi了。已经过 Android 5.0的小米4测试。(下载了1GB的文件以测试速度与稳定性)


via: http://www.linuxveda.com/2015/08/23/how-to-create-an-ap-in-ubuntu-15-04-to-connect-to-androidiphone/

作者:Sayantan Das 译者:jerryling315 校对:wxy

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

当你进入了 Linux 的世界,在下载安装 了某个 Linux 发行版,体验了 Linux 桌面并安装了一些你喜爱和需要的软件之后,应该去了解下 Linux 真正的魅力所在:命令行。每一个 Linux 命令其实就是一个程序,借助这些命令,我们可以办到非常多的事情。下面将会为大家介绍一下几个常用的命令。

如何寻求帮助?

在 Linux 下遇到问题,最重要的是要自己寻求帮助,下面是三种寻求帮助的方法。

man

man 是 Linux 的帮助手册,即 manual 。因为大多数程序都会自带手册,所以可以通过 man 命令获取帮助。执行以后,在 man page 页面中按 q 退出。

获取 ls 的帮助

$ man ls

查看有多少(针对不同方面的)同名的手册

$ man -f ls
ls (1)               - list directory contents
ls (1p)              - list directory contents

查看特定的手册

$ man 1p ls

info

man 不同的是,可以像浏览网页一样在各个节点中跳转。

从文档首页开始浏览

$ info

获取特定程序的帮助

$ info program

help

除了上面的两种方法外,还有一种简单使用的方法,那就是 --help 参数,一般程序都会有这个参数,会输出最简单有用的介绍。

$ man --help       ### 获取 man 的帮助
$ info --help      ### 获取 info 的帮助
$ ls --help        ### 获取 ls 的帮助

如何简单操作?

Terminal(终端) 中,有许多操作技巧,这里就介绍几个简单的。

光标

  • up(方向键上) 可以调出输入历史执行记录,快速执行命令
  • down(方向键下) 配合 up 选择历史执行记录
  • Home 移动光标到本行开头
  • End 移动光标到本行结尾
  • PgUp 向上翻页
  • PaDN 向下翻页
  • ctrl + c 终止当前程序

Tab 补全

Tab 补全是非常有用的一个功能,可以用来自动补全命令或文件名,省时准确。

  • 未输入状态下连按两次 Tab 列出所有可用命令
  • 已输入部分命令名或文件名,按 Tab 进行自动补全,多用你就肯定会喜欢的了。

常用命令

以下命令按照通常的使用频度排列。

cd

cd 是打开某个路径的命令,也就是打开某个文件夹,并跳转到该处。

$ cd path      ### path 为你要打开的路径。

其中 path 有绝对路径和相对路径之分,绝对路径强调从 / 起,一直到所在路径。相对路径则相对于当前路径来说,假设当前家目录有etc 文件夹(绝对路径应为 /home/username/etc),如果直接 cd etc 则进入此文件夹,但若是 cd /etc/ 则是进入系统 etc ,多琢磨一下就可以理解了。另外在 Linux 中, . 代表当前目录, .. 代表上级目录,因此返回上级目录可以 cd ..

ls

lslist ,列出文件。

$ ls       ### 仅列出当前目录可见文件
$ ls -l    ### 列出当前目录可见文件详细信息
$ ls -hl   ### 列出详细信息并以可读大小显示文件大小
$ ls -al   ### 列出所有文件(包括隐藏)的详细信息

注意: Linux 中 以 . 开头的文件或文件夹均为隐藏文件或隐藏文件夹。

pwd

pwd 用于返回当前工作目录的名字,为绝对路径名。

$ pwd
/home 

mkdir

mkdir 用于新建文件夹。

$ mkdir folder
$ mkdir -p folder/subfolder    ### -p 参数为当父目录存在时忽略,若不存在则建立,用此参数可建立多级文件夹

rm

rmremove ,删除文件。

$ rm filename      ### 删除 filename
$ rm -i filename   ### 删除 filename 前提示,若多个文件则每次提示
$ rm -rf folder/subfolder/  ### 递归删除 subfolder 下所有文件及文件夹,包括 subfolder 自身
$ rm -d folder     ###  删除空文件夹

cp

cpcopy ,复制文件。

$ cp source dest            ### 将 source 复制到 dest
$ cp folder/*  dest         ### 将 folder 下所有文件(不含子文件夹中的文件)复制到 dest
$ cp -r folder  dest        ### 将 folder 下所有文件(包含子文件夹中的所有文件)复制到 dest

mv

mvmove ,移动文件。

$ mv source  folder        ### 将 source 移动到 folder 下,完成后则为  folder/source
$ mv -i source folder      ### 在移动时,若文件已存在则提示 **是否覆盖** 
$ mv source dest           ### 在 dest 不为目录的前提下,重命名 source 为 dest

cat

cat 用于输出文件内容到 Terminal 。

$ cat /etc/locale.gen     ### 输出 locale.gen 的内容 
$ cat -n /etc/locale.gen  ### 输出 locale.gen 的内容并显示行号

more

morecat 相似,都可以查看文件内容,所不同的是,当一个文档太长时, cat 只能展示最后布满屏幕的内容,前面的内容是不可见的。这时候可用 more 逐行显示内容。

$ more /etc/locale.gen
$ more +100 /etc/locale.gen       ### 从 100 行开始显示

less

lessmore 相似,不过 less 支持上下滚动查看内容,而 more 只支持逐行显示。

$ less /etc/locale.gen
$ less +100 /etc/locale.gen

nano

nano 是一个简单实用的文本编辑器,使用简单。

$ nano  filename       ### 编辑 filename 文件,若文件不存在,则新打开一个文件,若退出时保存,则创建该文件

编辑完后,ctrl + X 提示是否保存,按 y 确定保存即可。

注意:在使用过程中可用 ctrl + G 获取帮助。

reboot

reboot 为重启命令。

# reboot         ### '$' 和 '#' 的区别在于 '$' 普通用户即可执行  
                 ### 而 '#' 为 root 用户才可执行,或普通用户使用 'sudo'

poweroff

poweroff 为关机命令。

# poweroff  ### 马上关机 

ping

ping 主要用于测试网络连通,通过对目标机器发送数据包来测试两台主机是否连通,及延时情况。

$ ping locez.com    ### 通过域名 ping,若 DNS 未设置好,可能无法 ping 通
$ ping linux.cn
PING linux.cn (211.157.2.94) 56(84) bytes of data.
64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=1 ttl=53 time=41.5 ms
64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=2 ttl=53 time=40.4 ms
64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=3 ttl=53 time=41.9 ms
^C
--- linux.cn ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 40.406/41.287/41.931/0.644 ms

$ ping 211.157.2.94   ### 通过 IP 地址 ping ,若无法 ping 通可能是网络连接出现问题

grep

grep 主要用于返回匹配的项目,支持正则表达式。

$ grep PATTERN filename      ### 返回所有含有 PATTERN 的行
$ grep zh_CN /etc/locale.gen ### 返回所有含 zh_CN 的行

mount

mount 用于挂载一个文件系统,需要 root 用户执行。一个磁盘可分为若干个分区,在分区上面可以创建文件系统,而挂载点则是提供一个访问的入口,将一个分区的文件系统挂载到某个目录中,称这个目录为挂载点,并且可以通过这个挂载点访问该文件系统中的内容。

例如一块硬盘在 Linux 中表示为 /dev/sda 那么它上面的分区应该表示为 /dev/sda1/dev/sda2

# mount                       ### 输出系统目前的挂载信息
# mount /dev/sda1 /mnt        ### 将 sda1 挂载到 /mnt 中
# cd /mnt                     ### 直接通过 /mnt 访问内容
# mount -o remount,rw  /mnt   ### 重新挂载 sda1 到 /mnt 并设置为 可读写 
# mount -a                    ### 挂载 fstab 文件配置好的文件系统

umount

umountmount 相反,是卸载一个挂载点,即取消该入口。

# umount /mnt                 ### 卸载 /mnt 这个挂载点的文件系统
# umount -a                   ### 卸载所有已挂载的文件系统 

tar

tar 主要用于创建归档文件,和解压归档文件,其本身是没有压缩功能的,但可以调用 gzipbzip2 进行压缩处理。
参数解释:

  • -c 创建归档
  • -x 解压归档
  • -v 显示处理过程
  • -f 目标文件,其后必须紧跟 目标文件
  • -j 调用 bzip2 进行解压缩
  • -z 调用 gzip 进行解压缩
  • -t 列出归档中的文件
$ tar -cvf filename.tar .       ### 将当前目录所有文件归档,但不压缩,注意后面有个 ’.‘ ,不可省略,代表当前目录的意思 
$ tar -xvf filename.tar         ### 解压 filename.tar 到当前文件夹
$ tar -cvjf filename.tar.bz2 .  ### 使用 bzip2 压缩
$ tar -xvjf  filename.tar.bz2   ### 解压 filename.tar.bz2 到当前文件夹
$ tar -cvzf filename.tar.gz     ### 使用 gzip  压缩
$ tar -xvzf filename.tar.gz     ### 解压 filename.tar.gz 到当前文件夹
$ tar -tf   filename            ### 只查看 filename 归档中的文件,不解压

ln

ln 主要用于在两个文件中创建链接,链接又分为 Hard Links (硬链接)和 Symbolic Links (符号链接或软链接),其中默认为创建硬链接,使用 -s 参数指定创建软链接。

  • 硬链接主要是增加一个文件的链接数,只要该文件的链接数不为 0 ,该文件就不会被物理删除,所以删除一个具有多个硬链接数的文件,必须删除所有它的硬链接才可删除。
  • 软链接简单来说是为文件创建了一个类似快捷方式的东西,通过该链接可以访问文件,修改文件,但不会增加该文件的链接数,删除一个软链接并不会删除源文件,即使源文件被删除,软链接也存在,当重新创建一个同名的源文件,该软链接则指向新创建的文件。
  • 硬链接只可链接两个文件,不可链接目录,而软链接可链接目录,所以软链接是非常灵活的。
$ ln source dest       ### 为 source 创建一个名为 dest 的硬链接
$ ln -s source dest    ### 为 source 创建一个名为 dest 的软链接

chown

chown 用于改变一个文件的所有者及所在的组。

# chown user filename        ### 改变 filename 的所有者为 user
# chown user:group filename  ### 改变 filename 的所有者为 user,组为 group
# chown -R root folder       ### 改变 folder 文件夹及其子文件的所有者为 root

chmod

chmod 永远更改一个文件的权限,主要有 读取写入执行 ,三种权限,其中 所有者用户组其他 各占三个,因此 ls -l 可以看到如下的信息

-rwxr--r-- 1 locez users   154 Aug 30 18:09 filename

其中 r=readw=writex=execute

# chmod +x filename        ### 为 user ,group ,others 添加执行权限
# chmod -x filename        ### 取消 user , group ,others 的执行权限
# chmod +w filename        ### 为 user 添加写入权限
# chmod ugo=rwx filename   ### 设置 user ,group ,others 具有 读取、写入、执行权限
# chmod ug=rw filename     ### 设置 user ,group 添加 读取、写入权限
# chmod ugo=--- filename   ### 取消所有权限

useradd

useradd 用于添加一个普通用户。

# useradd -m -g users -G audio -s /usr/bin/bash newuser     
### -m 创建 home 目录, -g 所属的主组, -G 指定该用户在哪些附加组, -s 设定默认的 shell ,newuser 为新的用户名

passwd

passwd 用于改变用户登录密码。

$ passwd                 ### 不带参数更改当前用户密码
# passwd newuser         ### 更改上述新建的 newuser 的用户密码 

whereis

whereis 用于查找文件、手册等。

$ whereis bash 
bash: /usr/bin/bash /etc/bash.bashrc /etc/bash.bash_logout /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz
$ whereis -b bash       ### 仅查找 binary
bash: /usr/bin/bash /etc/bash.bashrc /etc/bash.bash_logout
$ whereis -m bash       ### 仅查找 manual
bash: /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz

find

find 也用于查找文件,但更为强大,支持正则,并且可将查找结果传递到其他命令。

$ find . -name PATTERN    ### 从当前目录查找符合 PATTERN 的文件
$ find /home -name PATTERN -exec ls -l {} \;  # 从 /home 文件查找所有符合 PATTERN 的文件,并交由 ls 输出详细信息 

wget

wget 是一个下载工具,简单强大。

$ wget -O newname.md https://github.com/LCTT/TranslateProject/blob/master/README.md     ### 下载 README 文件并重命名为 newname.md
$ wget -c url     ### 下载 url 并开启断点续传

恭喜你,你已经学习了完了26 个基础的 Linux 命令。虽然这里只是一些最基础的命令,但是熟练使用这些命令就踏出了你从一位 Linux 新手成为 Linux 玩家的第一步!

在本篇中,我们将回顾一些系统管理员日常任务需要的技能。

RHCSA: Perform File and Directory Management – Part 2

RHCSA: 运行文件以及进行文件夹管理 - 第二部分

创建、删除、复制和移动文件及目录

文件和目录管理是每一个系统管理员都应该掌握的必备技能。它包括了从头开始的创建、删除文本文件(每个程序的核心配置)以及目录(你用来组织文件和其它目录),以及识别已有文件的类型。

touch 命令 不仅仅能用来创建空文件,还能用来更新已有文件的访问时间和修改时间。

touch command example

touch 命令示例

你可以使用 file [filename]来判断一个文件的类型 (在你用文本编辑器编辑之前,判断类型将会更方便编辑)。

file command example

file 命令示例

使用rm [filename] 可以删除文件。

Linux rm command examples

rm 命令示例

对于目录,你可以使用mkdir [directory]在已经存在的路径中创建目录,或者使用 mkdir -p [/full/path/to/directory]带全路径创建文件夹。

mkdir command example

mkdir 命令示例

当你想要去删除目录时,在你使用rmdir [directory] 前,你需要先确保目录是空的,或者使用更加强力的命令(小心使用它!)rm -rf [directory]。后者会强制删除[directory]以及它的内容,所以使用这个命令存在一定的风险。

输入输出重定向以及管道

命令行环境提供了两个非常有用的功能:允许重定向命令的输入和输出为另一个文件,以及发送命令的输出到另一个命令,这分别称为重定向和管道。

为了理解这两个重要概念,我们首先需要理解三个最重要的字符输入输出流类型,以 *nix 的话来说,它们实际上是特殊的文件。

  • 标准输入 (即 stdin),默认连接到键盘。 换句话说,键盘是输入命令到命令行的标准输入设备。
  • 标准输出 (即 stdout),默认连接到屏幕。 找个设备“接受”命令的输出,并展示到屏幕上。
  • 标准错误 (即 stderr),默认是命令的状态消息出现的地方,它也是屏幕。

在下面的例子中,ls /var的结果被发送到stdout(屏幕展示),ls /tecmint 的结果也一样。但在后一种情况下,它显示在标准错误输出上。

Linux input output redirect

输入和输出命令实例

为了更容易识别这些特殊文件,每个文件都被分配有一个文件描述符,这是用于访问它们的抽象标识。主要要理解的是,这些文件就像其它的一样,可以被重定向。这就意味着你可以从一个文件或脚本中捕获输出,并将它传送到另一个文件、命令或脚本中。这样你就可以在磁盘上存储命令的输出结果,用于稍后的分析。

要重定向 stdin (fd 0)、 stdout (fd 1) 或 stderr (fd 2),可以使用如下操作符。

转向操作效果
>重定向标准输出到一个文件。如果目标文件存在,内容就会被重写。
>>添加标准输出到文件尾部。
2>重定向标准错误输出到一个文件。如果目标文件存在,内容就会被重写。
2>>添加标准错误输出到文件尾部。
&>重定向标准错误和标准输出到一个文件。如果目标文件存在,内容就会被重写。
<使用特定的文件做标准输入。
<>使用特定的文件做标准输入和标准输出。

与重定向相比,管道是通过在命令后和另外一个命令前之间添加一个竖杠(|)

记得:

  • 重定向是用来定向命令的输出到一个文件,或把一个文件发送作为到一个命令的输入。
  • 管道是用来将命令的输出转发到另一个命令作为其输入。

重定向和管道的使用实例

例1:将一个命令的输出到文件

有些时候,你需要遍历一个文件列表。要做到这样,你可以先将该列表保存到文件中,然后再按行读取该文件。虽然你可以直接遍历ls的输出,不过这个例子是用来说明重定向。

# ls -1 /var/mail > mail.txt

Redirect output of command tot a file

将一个命令的输出重定向到文件

例2:重定向stdout和stderr到/dev/null

如果不想让标准输出和标准错误展示在屏幕上,我们可以把这两个文件描述符重定向到 /dev/null。请注意对于同样的命令,重定向是如何改变了输出。

# ls /var /tecmint
# ls /var/ /tecmint &> /dev/null

Redirecting stdout and stderr ouput to /dev/null

重定向stdout和stderr到/dev/null

例3:使用一个文件作为命令的输入

cat 命令的经典用法如下

# cat [file(s)]

您还可以使用正确的重定向操作符发送一个文件作为输入。

# cat < mail.txt

Linux cat command examples

cat 命令实例

例4:发送一个命令的输出作为另一个命令的输入

如果你有一个较大的目录或进程列表,并且想快速定位,你或许需要将列表通过管道传送给grep。

接下来我们会在下面的命令中使用管道,第一个管道是查找所需的关键词,第二个管道是除去产生的 grep command。这个例子列举了所有与apache用户有关的进程:

# ps -ef | grep apache | grep -v grep

Send output of command as input to another

发送一个命令的输出作为另一个命令的输入

归档,压缩,解包,解压文件

如果你需要传输、备份、或者通过邮件发送一组文件,你可以使用一个存档(或打包)工具,如 tar,通常与gzip,bzip2,或 xz 等压缩工具配合使用。

您选择的压缩工具每一个都有自己不同的压缩速度和压缩率。这三种压缩工具,gzip是最古老和可以较小压缩的工具,bzip2提供经过改进的压缩,以及xz是最新的而且压缩最大。通常情况下,使用这些压缩工具压缩的文件的扩展名依次是.gz、.bz2或.xz。

命令缩写描述
–createc创建一个tar归档
–concatenateA添加tar归档到另外一个归档中
–appendr添加非tar归档到另外一个归档中
–updateu添加比归档中的文件更新的文件
–diff or –compared将归档中的文件和硬盘的文件进行对比
–listt列举一个tar压缩包的内容
–extract or –getx从归档中提取文件
操作参数缩写描述
—directory dirC在执行操作前更改目录
—same-permissions and —same-ownerp分别保留权限和所有者信息
–verbosev列举所有读取或提取的文件,如果和 --list 参数一起使用,也会显示文件的大小、所有权和时间戳
—exclude file从存档中排除文件。在这种情况下,文件可以是一个实际的文件或匹配模式。
—gzip or —gunzipz使用gzip压缩归档
–bzip2j使用bzip2压缩归档
–xzJ使用xz压缩归档

例5:创建一个tar文件,然后使用三种压缩工具压缩

在决定使用这个还是那个工具之前,您可能想比较每个工具的压缩效率。请注意压缩小文件或几个文件,结果可能不会有太大的差异,但可能会给你看出它们的差异。

# tar cf ApacheLogs-$(date +%Y%m%d).tar /var/log/httpd/*        # Create an ordinary tarball
# tar czf ApacheLogs-$(date +%Y%m%d).tar.gz /var/log/httpd/*    # Create a tarball and compress with gzip
# tar cjf ApacheLogs-$(date +%Y%m%d).tar.bz2 /var/log/httpd/*   # Create a tarball and compress with bzip2
# tar cJf ApacheLogs-$(date +%Y%m%d).tar.xz /var/log/httpd/*    # Create a tarball and compress with xz

Linux tar command examples

tar 命令实例

例6:归档时同时保存原始权限和所有权

如果你正在从用户的主目录创建备份,你需要要存储的个人文件与原始权限和所有权,而不是通过改变它们的用户帐户或守护进程来执行备份。下面的命令可以在归档时保留文件属性。

# tar cJf ApacheLogs-$(date +%Y%m%d).tar.xz /var/log/httpd/* --same-permissions --same-owner

创建软连接和硬链接

在Linux中,有2种类型的链接文件:硬链接和软(也称为符号)链接。因为硬链接文件只是现存文件的另一个名字,使用相同的 inode 号,它指向实际的数据;而符号链接只是指向的文件名。

此外,硬链接不占用磁盘上的空间,而符号链接则占用少量的空间来存储的链接本身的文本。硬链接的缺点就是要求它们必须在同一个文件系统内,因为 inode 在一个文件系统内是唯一的。而符号链接没有这个限制,它们通过文件名而不是 inode 指向其它文件或目录,所以可以跨文件系统。

创建链接的基本语法看起来是相似的:

# ln TARGET LINK_NAME               #从LINK_NAME到Target的硬链接
# ln -s TARGET LINK_NAME            #从LINK_NAME到Target的软链接

例7:创建硬链接和软链接

没有更好的方式来形象的说明一个文件和一个指向它的硬链接或符号链接的关系,而不是创建这些链接。在下面的截图中你会看到文件和指向它的硬链接共享相同的inode,都是使用了相同的466个字节的磁盘。

另一方面,在别的磁盘创建一个硬链接将占用5个字节,这并不是说你将耗尽存储容量,而是这个例子足以说明一个硬链接和软链接之间的区别。

Difference between a hard link and a soft link

软连接和硬链接之间的不同

在Linux系统上符号链接的典型用法是指向一个带版本的文件。假设有几个程序需要访问文件fooX.Y,但麻烦是版本经常变化(像图书馆一样)。每次版本更新时我们都需要更新指向 fooX.Y 的单一引用,而更安全、更快捷的方式是,我们可以让程序寻找名为 foo 的符号链接,它实际上指向 fooX.Y。

这样的话,当你的X和Y发生变化后,你只需更新符号链接 foo 到新的目标文件,而不用跟踪每个对目标文件的使用并更新。

总结

在这篇文章中,我们回顾了一些基本的文件和目录管理技能,这是每个系统管理员的工具集的一部分。请确保阅读了本系列的其它部分,并将这些主题与本教程所涵盖的内容相结合。

如果你有任何问题或意见,请随时告诉我们。我们总是很高兴从读者那获取反馈.


via: http://www.tecmint.com/file-and-directory-management-in-linux/

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

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