Linux makefile分析

阅读数:246 评论数:0

跳转到新版页面

分类

Linux

正文

一、概述   

Linux内核源码中含有很多个Makefile文件,这些Makefile文件又要包含其他一些文件(比如配置信息、通用规则 等)。这些文件构成了Linux的Makefile体系。

名称 描述
顶层Makefile 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
.config 配置文件,在配置内核时生成。所有Makefile(包括顶层目录及各级子目录)都是根据.config来决定使用哪些文件
arc/${ARCH}/Makefile 对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像
scripts/Makefile.* Makefile共用的通用规则、脚本等
kbuild Makefiles 各级子目录下的Makefile,它们相对简单,被上一层Makefile调用来编译当前目录下的文件

    内核文档Documentation/kbuild/makefiles.txt对内核中Makefile的作用、用法讲解得非常透彻。

二、决定编译哪些文件

    Linux内核的编译过程从顶层Makefile开始,然后递归地进入各级子目录调用它们的Makefile,分为3个步骤:

(1)顶层Makefile决定内核根目录哪些子目录将被编译进内核。

(2)arch/${ARCH}/Makefile决定arch/${ARCH}目录下哪些文件、哪些目录被编译进内核。

(3)各级子目录的Makefile决定所在目录下哪些文件将被 编进内核,哪些文件将被编成模块(即驱动程序 ),进入哪些子目录继续调用它们的Makefile。

    先来看步骤(1),在顶层Makefile中可以看到如下内容:

init-y		:= init/
drivers-y	:= drivers/ sound/ firmware/
net-y		:= net/
libs-y		:= lib/
core-y		:= usr/
virt-y		:= virt/
...
core-y		+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/

    除了include目录,还有一个arch目录没有出现在内核中,它在arc/${ARCH}/Makefile中被包含进内核,在顶层Makefile中直接包含了这个Makefile,如下所示:

SRCARCH 	:= $(ARCH)
...
include arch/$(SRCARCH)/Makefile

    对于ARCH变量,可以在执行make命令时传入,比如make ARCH=arm ...。另外,对于非x86平台,还需要指定交叉编译工具,这也可以在执行make命令进传入,比如make CROSS_CoMPILE=arm-linux-...。为了方便,常在顶层Makefile中进行如下修改

#修改前
ARCH    ?=$(SUBARCH)
CROSS_COMPILE ?=$(CONFIG_CROSS_COMPILE:"%"=%)
#修改后
ARCH  ?= arm
CROSS_COMPILE ?=arm-linux-

对于步骤(2)的arch/$(ARCH)/Makefile,以ARM体系为例,在arch/arm/Makefile中可以看到以下内容:

head-y		:= arch/arm/kernel/head$(MMUEXT).o
...
core-$(CONFIG_FPE_NWFPE)	+= arch/arm/nwfpe/
core-$(CONFIG_FPE_FASTFPE)	+= $(FASTFPE_OBJ)
core-$(CONFIG_VFP)		+= arch/arm/vfp/
core-$(CONFIG_XEN)		+= arch/arm/xen/
core-$(CONFIG_KVM_ARM_HOST) 	+= arch/arm/kvm/
core-$(CONFIG_VDSO)		+= arch/arm/vdso/
...
core-y				+= arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y				+= arch/arm/probes/
core-y				+= arch/arm/net/
core-y				+= arch/arm/crypto/
core-y				+= arch/arm/firmware/
core-y				+= $(machdirs) $(platdirs)
...
libs-y				:= arch/arm/lib/ $(libs-y)

    MMUEXT在arch/arm/Makefile前面定义,对于没有MMU的处理器,MMUEXT的值为-nommu,使用文件 head-nommu.S,对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。

    core-y、libs-y的内容,这些都是体系结构相关的目录。

    编译内核时,将依次进入init-y、core-y,drivers-y和net-y所列出的目录中执行它们的Makefile,每个子目录会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件),最后,head-y所表示的文件将和这些build-in.o、lib.a一起被连接成内核映像文件vmlinux。

    最后,看一下步骤(3)是怎么进行的。

    在配置内核时,生成配置文件.config,内核顶层Makefile使用如下语句间接包含.config文件,以后根据.config中定义的各个变量决定编译哪些文件。之所以说是间接包含,是因为包含的include/config/auto.conf文件,而它只是将.config文件中的注释去掉,并根据顶层的Makefile中定义的变量增加了一些变量而已。

# Read in config
-include include/config/auto.conf

    在include/config/auto.conf文件中,变量的值主要有两类:“y”、“m”。各级子目录的Makefiel使用这些变量来决定哪些文件被编译进内核,哪些文件被编译成模块,要进入哪些下级子目录继续编译。

(1)obj-y用来定义哪些文件被编进内核

    obj-y中定义的.o文件由当前目录下的.c或.S文件编译生成,它们连同下级子目录的built-in.o文件一起被组合成(使用$(LD) -r)当前目录下的built-in.o文件。这个built-in.o文件将被它的上一层Makefile使用。

    obj-y中各个.o文件的顺序是有意义的,因为内核中使用module_init()或__initcall定义的函数将按照它们的连接顺序被调用。

例:

obj-$(CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

   当上面的CONFIG_ISDN、CONFIG_ISDN_PPP_BSDCOMP在.config中被定义为y时isdn.c或isdn.S、isdn_bsdcomp.c或isdn_bsdcomp.S被编译成isdn.o、isdn_bsdcomp.o。这两个.o文件被组合进built-in.o文件中,最后连接进入内核。假如isdn.o isdn_bsdcomp.o中分别用module_init(A) module_init(B)定义了函数A、B,则内核启动时A先被调用、然后是B。

(2)obj-m用来定义哪些文件被编译成可加载的模块

    obj-m中定义的.o文件由当前目录下的.c或.S文件编译生成,它们不会被编进build-in.o中,而是被编成可加载模块。

    一个模块可以由一个或几个.o文件组成。对于只有一个源文件的模块,在obj-m中直接增加它的.o文件即可。对于有多个源文件的模块,除在obj-m中增加一个.o文件外,还要定义一个<module_name>-objs变量来告诉Makefil这个.o文件由哪些文件组成。

例1:

#当CONFIG_ISDN_PPP_BSDCOMP在.config文件中被定义为m时,isdn_bsdcomp.c或isdn_bsdcomp.S
#将被编译成isdn_bsdcomp.o文件
obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o

例2

#当下面的CONFIG_ISDN在.config文件中被定义为m时,将会生成一个isdn.o文件,
#它由isdn-objs中定义的isdn_net_lib.o isdn_v110.o isdn_common.o等
#3个文件组合
obj-$(CONFIG_ISDN) += isdn.o
isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o

(3)lib-y用来定义哪些文件被编译成库文件

    lib-y中定义的.o文件由当前目录下的.c或.S文件编译生成,它们被打包成当前目录下的一个库文件:lib.a。

    同时出现在obj-y、lib-y中的.o文件,不会被包含进lib.a中。

    要把这个lib.a编进内核中,需要在顶层Makefile中libs-y变量中列出当前目录。要编进库文件的内核代码一般都在这两个目录下:lib/ 、arch/$(ARCH)/lib

(4)obj-y、obj-m还可以用来指定要进入的下一层子目录

    Linux中一个Makefile文件只负责生成当前目录下的目标文件,子目录下的目标文件由子目录的Makefile生成。Linux的编译系统会自动进入这些子目录调用它们的Makefile,只是在这之前指定这些子目录。

    这要用到obj-y、obj-m,只要在其中增加这些子目录名即可。

例子:

#fs/Makefile中有如下一行
obj-$(CONFIG_JFFS2_FS)		+= jffs2/

三、怎么编译这些文件

    即编译选项、连接选项是什么。这些选项分为3类:全局的,适用于整个内核代码;局部的,仅适用于某个Makefile中的所有文件;个体的,仅适用于某个文件。

    全局选项在顶层Makefile和arch/$(ARCH)/Makefile中定义,这些选项的名称为CFLAG、AFLAG、LDFLAG、ARFLAG,它们分别是编译C文件的选项、编译汇编文件的选项、连接文件的选项、制作库文件的选项。

    需要使用局部选项时,它们在各个子目录中定义,名称为:EXTRA_CFLAGS、EXTRA_AFLAGS、EXTRA_LDFLAGS、EXTRA_ARFLAGS,它们的用途与前述选项相同,只是作用范围比较小,它们针对当前Makefile中的所有文件。

    另外,如果想针对某个文件定义它的编译选项,可以使用CFLAG_$@, AFLAGS_$@,前者用于编译某个C文件,后者用编译某个汇编文件。$@表示某个文件名,比如

CFLAGS_aha152x.o = -DAHA152X_STAT -DAUTOCONF

    需要注意的是,这3类选项是一起使用的。

四、怎么连接这些文件,它们的顺序如何

      前面分析有哪些文件要编译进内核,顶层的Makefile和arch/$(ARCH)/Makefile定义几类目录(或文件):head-y, init-y, drivers-y, net-y, libs-y, core-y。除head-y外,其余的都是目录名。这些目录的名面直接加上了built-in.a或lib.a,表示要链接进内核的文件

# 以init-y为例,其实就是变成了init-y/built-in.a
init-y		:= $(patsubst %/, %/built-in.a, $(init-y))
core-y		:= $(patsubst %/, %/built-in.a, $(core-y))
drivers-y	:= $(patsubst %/, %/built-in.a, $(drivers-y))
net-y		:= $(patsubst %/, %/built-in.a, $(net-y))
libs-y1		:= $(patsubst %/, %/lib.a, $(libs-y))
libs-y2		:= $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y		:= $(patsubst %/, %/built-in.a, $(virt-y))

顶层Makefile再往下看

export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds

对于ARM体系,连接脚本就是arch/$(ARM)/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,规则在scripts/Makefile.build中

$(obj)/%.lds: $(src)/%.lds.S FORCE
	$(call if_changed_dep,cpp_lds_S)

 

顶层makefile

先是定义了内核的版本,接下来会自到很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_VERSION就会为0。然后根据KBUILD_VERBOSE的值来决定quiet和Q的值,符号@控制命令的输出,如果它被放在一个命令之前,这条命令的输出将会是CC scripts/mod/empty.o,而不是Compiling...scripts/mod/empty.o,在这段最后,导出了所有的变量。

下一个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时总是空的),我们会设置变量KBULD_OUTPUT为传递选项O的值(如果这个选项项被传递进来了),下一步会检查变量KBUILD_OUTPUT,如果已经设置好那么接下来会做以下几件事:

(1)将变量KBUILD_OUTPUT的值保存到临时变量saved-output

(2)尝试创建给定的输出目录

(3)检查 创建的输出目录,如果失败了就打印错误

(4)如果成功创建了输出目录,那么就在新目录重新执行make命令

下一个ifeq语句检查传递给make的选项C和M

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命令的目录,并且将这些变量输出。

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

include scripts/Kbuild.include

文件Kbuild或者又叫做Kernel Build System是一个用来管理构建内核及模块的特殊框架。kbuild文件的语法与makefile一样。文件scripts/Kbuild.include为kbuild系统提供了一些常规的定义。

接着就是要获取SUBARCH的值,这个变量代表了当前系统架构(一般指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之后就要设置SRCARCH,SRCARCH提供了硬件架构相关的代码的目录。

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

如果没有设置过代表内核配置文件路径的变量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)

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

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

接着又定义了两个变量: USERINCLUDE和LINUXINCLUDE,它们包含了头文件的路径(第一个是组用户用的,第二个是给内核用的)

# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE    := \
		-I$(srctree)/arch/$(SRCARCH)/include/uapi \
		-I$(objtree)/arch/$(SRCARCH)/include/generated/uapi \
		-I$(srctree)/include/uapi \
		-I$(objtree)/include/generated/uapi \
                -include $(srctree)/include/linux/kconfig.h

# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
LINUXINCLUDE    := \
		-I$(srctree)/arch/$(SRCARCH)/include \
		-I$(objtree)/arch/$(SRCARCH)/include/generated \
		$(if $(KBUILD_SRC), -I$(srctree)/include) \
		-I$(objtree)/include \
		$(USERINCLUDE)

以及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_IGNORE和RCS_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

再往后,我们就就看到变量KBUILD_MODULES和KBUILD_BUILTIN的定义,这两上变量决定了我们要编译会么东西(内核、模块或者两者)

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

如果仅仅传递了modules给make,变量KBUILD_BUILTIN会依赖于内核配置选项CONFIG_MODVERSIONS。

直接到

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

从export RCS_FIND_IGNORE到all:vmlinux这一部分的makefile代码,主要负责根据各种配置文件 生成不同目标内核。

目标all是在命令行如果不指定具体目标时默认使用的目标。目标all依赖于根makefile后面声明的vmlinux

vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(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文件里。

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

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

vmlinux-dir依赖于两个部分:prepare和scripts。第一个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,后者又展开到archheader和archscripts,这两个变量定义在x86_64相关的Makefile。X86_64特定的makefile从变量定义开始,这些变量都是和特定架构的配置(defconfig等)有关联。

第一个目标是makefile生成的系统调用列表中的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

script/basic/Makefile包含了编译两个主机程序fixdep和bin2的目标

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,它是一个用来将标准输入stdin收到的二进制流通过标准输出接口stdout转换成C头文件的非常小的C程序。hostprog-y告诉kbuild这里有个名为fixed的程序,这个程序会通过和Makefile相同目录 的fixdep.c编译而来。

当目标scripts_basic被执行,目标archscripts就会make arch/x86/tools下的makefile和目标relocs,包含了重定位的信息的代码relocs_32.c和relocs_64.c将会被编译。

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

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

在内核的根Makefile使用arch/x86/include/generated/asm的目标asm-generic来构建generic汇编头文件。这样archprepare就完成了,prepare0会接着被执行

 




相关推荐

一、概述 makefile文件包含一些规则,告诉make工具编译哪些文件以及怎样编译这些文件。 配置文件.config 是在进行内核配置后生成的中间产物。 是通过make menuconfi

一、概述 1、为什么Linus不使用GPLv3 在 PC 上,只要你得到了某个程序的源代码,就可以自行编译生成二进制程序,然后替换掉原有的二进制程序,你的程序自由很容易得到保证。然而 iPod、iPh

说明:这种方式只是用于方便阅读代码,因为可以在源代间快速索引跳跃。但是最后可能会有一些warning,可以不必关心,如果是强迫症,可以使用下面这种方式来去掉。

一、概述 在Linux系统中,/usr/bin和/usr/local/bin是两个常见的目录,用于存放可执行文件(二进制文件)。 很多应用都安装在/usr/local下面,先看一下automake工具

  一、概述 vmstat命令是最常见的Linux/Unix监控工具,可以监控给定时间间隔服务器的CPU使用率、内存使用、IO情况。相比top命令,可以查看到整个机器的CPU、内存、IO的使用情况,而

一、概述 sar,System Activity Reporter。是目前 Linux 上最为全面的系统性能分析工具之一,可以从多方面对系统的活动进行报告,包括:文件的读写情况、系统调用的使用情况、磁

一、简介 简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。 二、使用方法 1、基本语法  awk '条件类型1 {动作1} 条件类型2{动作2} ...

一、概述 列出目标目录中所有的子目录和文件。 二、 语法 ls [选项] [目录名] -a, –all 列出目录下的所有文件,包括以 . 开头的隐含文件 -A 同-a,但不列出“.”(表示当前目录)

一、概述 cd全称是change directory,用于切换当前工作目录。 注意的是,cd命令是一个内建命令,它是由 shell 提供的。因此,不同的 shell 可能会有一些差异,但基本的用法和功

一、概述 全称为print working directory,查看”当前工作目录“的完整路径,一般情况下不带任何参数 二、语法 pwd [选项] -L 即logical,逻辑路径 -P 即