赞
踩
作者:解琛
时间:2020 年 7 月 15 日
先掌握全局,然后再深挖其中重要的细节。
使用 which 命令可以查看指定命令的路径。
which ls
“-” :减号,不是目录,但作为 cd 命令的参数时可以返回上一次 cd 切换前的目录。
cd -
“-p” 选项可以不输入,若使用了 “-p” 选项, 当创建的目录名包含的上级目录不存在时,它会自动创建所有不存在的目录。
mkdir [-p] 目录名
touch命令可以创建不存在的文件;
touch通过参数修改目录或文件的日期时间,就是摸一下,更新它的时间。
rmdir [-p] 目录名
它只能用来删除空的目录,-p可以用来递归删除目录,如果子目录删除后其父目录为空时,也一同被删除。
!! 代表上一条指令。
sudo !!
# sudo加两个感叹号,重新使用sudo权限执行上一条命令
使用 poweroff 可以关机。
通过 -s (可省略)指定搜索章节的内容,man 手册的章节 3 是“库调用”相关的函数说明。
man -s 3 printf
命令 | 作用 |
---|---|
apt-cache showsrc 软件包名 | 显示软件包的相关信息,如版本信息,依赖关系等 |
apt-cache search 软件包名 | 按关键字查找软件包 |
apt-cache depends软件包名 | 显示该软件包的依赖关系信息 |
apt-cache rdepends软件包名 | 显示所有依赖于该软件包的软件包名字 |
apt-cache show 软件包名 | 显示指定软件包的信息,如版本号,依赖关系等. |
apt-cache pkgnames | 显示所有软件包的名字 |
apt-cache policy 软件包名 | 显示软件包的安装状态 |
命令 | 作用 |
---|---|
apt install 软件包名 | 安装指定的软件包 |
apt remove 软件包名 | 卸载指定的软件包 |
apt update | 更新软件源列表 |
apt search 软件包名 | 根据关键字搜索对应的软件包 |
apt show 软件包名 | 显示软件包的相关信息 |
apt list | 根据名称列出所有的软件包 |
usermod 修改用户名;
passwd 修改密码;
hostname 修改主机名。
uname -r
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
可以使用 evtest 工具更方便地查看硬件当前接入的输入设备, 并且对其进行实时检测。
sudo apt install evtest
xiechen@xiechen-Ubuntu:/dev/input$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: Power Button
/dev/input/event1: Sleep Button
/dev/input/event2: AT Translated Set 2 keyboard
/dev/input/event3: Video Bus
/dev/input/event4: ImExPS/2 Generic Explorer Mouse
/dev/input/event5: VirtualBox USB Tablet
/dev/input/event6: VirtualBox mouse integration
Select the device event number [0-6]:
执行命令后,它会扫描 /dev/input 目录下的 event 设备输入事件文件,并列出到终端, 它提示我们可以通过数字选择对应的设备进行测试。
Linux 没有提供类似 Windows 的任务管理器,但是它将系统运行的信息都记录在 /proc 目录下的文件中, 用户可以通过访问该目录下的文件获取对应的系统信息。
xiechen@xiechen-Ubuntu:/proc$ ls
1 12 141 153 174 19845 21046 29 352 372 39 40 4118 5719 6140 6201 6329 7864 942 dma kmsg partitions timer_list
10 1216 14117 154 178 1995 21337 294 355 3728 3904 4041 4121 6 6142 6202 6354 7875 950 driver kpagecgroup pressure tty
1018 12633 14138 155 17842 2 21436 3 357 373 3909 4044 4125 6005 6147 6207 6356 7883 957 execdomains kpagecount sched_debug uptime
10303 12681 142 156 17845 20 22 30 358 3748 391 4048 4129 6008 6149 6266 6376 7892 acpi fb kpageflags schedstat version
10888 13 143 1560 18 20306 23 3028 3595 3786 3911 4051 4131 6009 6152 6267 6381 7911 asound filesystems loadavg scsi version_signature
10940 13172 144 157 18280 20340 24 308 3596 3788 3914 4052 42 6026 6154 6272 6383 862 buddyinfo fs locks self vmallocinfo
10961 1325 145 159 18885 20379 251 32 36 3791 3916 4053 515 6028 6158 6273 7074 867 bus interrupts mdstat slabinfo vmstat
11 136 14723 16 1898 2039 252 320 362 38 392 4054 551 6041 6159 6276 755 8837 cgroups iomem meminfo softirqs zoneinfo
11063 137 14733 161 19127 20469 256 33 3649 3800 394 4059 5690 6044 6173 6279 758 885 cmdline ioports misc stat
11064 138 14745 164 194 20479 26 34 365 382 395 4080 5691 6068 6182 6280 7810 9 consoles irq modules swaps
1123 1395 148 16504 19419 2078 27 347 3665 3850 396 4095 5692 607 6194 6281 7818 905 cpuinfo kallsyms mounts sys
1125 1398 149 16991 19437 20826 277 3497 3674 386 3977 41 5693 608 6195 6284 7852 911 crypto kcore mtrr sysrq-trigger
1131 14 15 17 19454 2088 278 35 368 387 3995 4111 5694 6105 6196 6302 7857 926 devices keys net sysvipc
1133 140 152 17110 19465 21 28 350 37 3893 4 4116 5718 6114 6197 6307 7861 929 diskstats key-users pagetypeinfo thread-self
/sys 目录下的文件 / 文件夹向用户提供了一些关于设备、内核模块、文件系统以及其他内核组件的信息。
xiechen@xiechen-Ubuntu:/sys$ ls
block bus class dev devices firmware fs hypervisor kernel module power
xiechen@xiechen-Ubuntu:/sys$ ls class
ata_device block devfreq-event extcon hwmon leds nd powercap printer remoteproc scsi_generic thermal vfio
ata_link bsg dma firmware i2c-adapter mdio_bus net power_supply ptp rfkill scsi_host tpm virtio-ports
ata_port dax dmi gpio i2c-dev mem pci_bus ppdev pwm rtc sound tpmrm vtconsole
backlight devcoredump drm graphics input misc pci_epc ppp rapidio_port scsi_device spi_master tty wakeup
bdi devfreq drm_dp_aux_dev hidraw iommu mmc_host phy pps regulator scsi_disk spi_slave vc watchdog
/dev 目录包含了非常丰富的设备信息,该目录下包含了 Linux 系统中使用的所有外部设备, 如 /dev/tty 为串口设备、/dev/ram 为内存、通过这些设备文件,我们也可以访问到对应的硬件设备。
使用软件 etcher 可以轻松完成镜像的刻录。
sudo apt install nfs-kernel-server
在配置 NFS 时需要使用到用户 uid 和组 gid,可使用 id 命令查看。
xiechen@xiechen-Ubuntu:~$ id
uid=1000(xiechen) gid=1000(xiechen) 组=1000(xiechen),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare),127(docker)
安装NFS服务后,会新增一个 /etc/exports 文件,NFS 服务根据它的配置来运行,它默认包含了一些配置的范例。
xiechen@xiechen-Ubuntu:~$ cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
详细的帮助说明可以使用 命令 man nfs 查看。
/home/embedfire/workdir 192.168.0.0/24(rw,sync,all_squash,anonuid=998,anongid=998,no_subtree_check)
sudo exportfs -arv
showmount -e
sudo apt install nfs-common -y
showmount -e 192.168.0.219
sudo mount -t nfs 192.168.0.219:/home/embedfire/workdir /mnt
若挂载成功,终端不会有输出,Linux 的哲学思想是 “没有消息便是好消息”。
sudo umount /mnt
GCC 编译工具链(toolchain)是指以 GCC 编译器为核心的一整套工具,用于把源代码转化成可执行应用程序。它主要包含以下三部分内容:
系统默认的 Binutils 工具集位于 /usr/bin 目录下,可使用如下命令查看系统中存在的 Binutils 工具集。
xiechen@xiechen-Ubuntu:~/文档/6.全世科技/3.2212协议/c$ ls /usr/bin/ | grep linux-gnu- i686-linux-gnu-pkg-config x86_64-linux-gnu-addr2line x86_64-linux-gnu-ar x86_64-linux-gnu-as x86_64-linux-gnu-c++filt x86_64-linux-gnu-cpp x86_64-linux-gnu-cpp-7 x86_64-linux-gnu-dwp x86_64-linux-gnu-elfedit x86_64-linux-gnu-g++ x86_64-linux-gnu-g++-7 x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc-7 x86_64-linux-gnu-gcc-ar x86_64-linux-gnu-gcc-ar-7 x86_64-linux-gnu-gcc-nm x86_64-linux-gnu-gcc-nm-7 x86_64-linux-gnu-gcc-ranlib x86_64-linux-gnu-gcc-ranlib-7 x86_64-linux-gnu-gcov x86_64-linux-gnu-gcov-7 x86_64-linux-gnu-gcov-dump x86_64-linux-gnu-gcov-dump-7 x86_64-linux-gnu-gcov-tool x86_64-linux-gnu-gcov-tool-7 x86_64-linux-gnu-gold x86_64-linux-gnu-gprof x86_64-linux-gnu-ld x86_64-linux-gnu-ld.bfd x86_64-linux-gnu-ld.gold x86_64-linux-gnu-nm x86_64-linux-gnu-objcopy x86_64-linux-gnu-objdump x86_64-linux-gnu-pkg-config x86_64-linux-gnu-python2.7-config x86_64-linux-gnu-python3.6-config x86_64-linux-gnu-python3.6m-config x86_64-linux-gnu-python3-config x86_64-linux-gnu-python3m-config x86_64-linux-gnu-python-config x86_64-linux-gnu-ranlib x86_64-linux-gnu-readelf x86_64-linux-gnu-size x86_64-linux-gnu-strings x86_64-linux-gnu-strip x86_64-pc-linux-gnu-pkg-config
xiechen@xiechen-Ubuntu:~/文档/6.全世科技/3.2212协议/c$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.2) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.5.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
本系统中使用的 glibc 是 2.27 版本,是由 GCC 7.5.0 版本的编译器编译出来的。
#直接编译成可执行文件
gcc hello.c -o hello
#以上命令等价于执行以下全部操作
#预处理,可理解为把头文件的代码汇总成C代码,把*.c转换得到*.i文件
gcc –E hello.c –o hello.i
#编译,可理解为把C代码转换为汇编代码,把*.i转换得到*.s文件
gcc –S hello.i –o hello.s
#汇编,可理解为把汇编代码转换为机器码,把*.s转换得到*.o,即目标文件
gcc –c hello.s –o hello.o
#链接,把不同文件之间的调用关系链接起来,把一个或多个*.o转换成最终的可执行文件
gcc hello.o –o hello
GCC 编译工具链在编译一个 C 源文件时需要经过以下 4 步:
xiechen@xiechen-Ubuntu:~/2.测试中心$ ls -l
总用量 872
-rw-r--r-- 1 xiechen xiechen 79 8月 5 16:38 main.c
-rw-r--r-- 1 xiechen xiechen 17935 8月 5 16:40 main.i
-rw-r--r-- 1 xiechen xiechen 1536 8月 5 16:43 main.o
-rw-r--r-- 1 xiechen xiechen 459 8月 5 16:43 main.s
-rwxr-xr-x 1 xiechen xiechen 8296 8月 5 16:47 main_so
-rwxr-xr-x 1 xiechen xiechen 844704 8月 5 16:46 main_static
链接分为两种:
xiechen@xiechen-Ubuntu:~/2.测试中心$ gcc main.o -o main_so
xiechen@xiechen-Ubuntu:~/2.测试中心$ gcc main.o -o main_static --static
xiechen@xiechen-Ubuntu:~/2.测试中心$ ls -l
总用量 872
-rw-r--r-- 1 xiechen xiechen 79 8月 5 16:38 main.c
-rw-r--r-- 1 xiechen xiechen 17935 8月 5 16:40 main.i
-rw-r--r-- 1 xiechen xiechen 1536 8月 5 16:43 main.o
-rw-r--r-- 1 xiechen xiechen 459 8月 5 16:43 main.s
-rwxr-xr-x 1 xiechen xiechen 8296 8月 5 16:49 main_so
-rwxr-xr-x 1 xiechen xiechen 844704 8月 5 16:50 main_static
使用动态链接生成的 main.so 程序才 8.2KB, 而使用静态链接生成的 main_static 程序则高达 825KB。
使用 ldd 工具查看动态文件的库依赖
xiechen@xiechen-Ubuntu:~/2.测试中心$ ldd main_so
linux-vdso.so.1 (0x00007ffd953e7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f75b81b5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f75b87a8000)
xiechen@xiechen-Ubuntu:~/2.测试中心$ ldd main_static
不是动态可执行文件
sudo apt install gcc-arm-linux-gnueabihf
xiechen@xiechen-Ubuntu:~/2.测试中心$ arm-linux-gnueabihf-gcc -v
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabihf-gcc
目标:arm-linux-gnueabihf
配置为:'/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/snapshots/gcc.git~linaro-6.3-2017.05/configure' SHELL=/bin/bash --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libmudflap --enable-lto --enable-shared --without-included-gettext --enable-nls --disable-sjlj-exceptions --enable-gnu-unique-object --enable-linker-build-id --disable-libstdcxx-pch --enable-c99 --enable-clocale=gnu --enable-libstdcxx-debug --enable-long-long --with-cloog=no --with-ppl=no --with-isl=no --disable-multilib --with-float=hard --with-fpu=vfpv3-d16 --with-mode=thumb --with-tune=cortex-a9 --with-arch=armv7-a --enable-threads=posix --enable-multiarch --enable-libstdcxx-time=yes --enable-gnu-indirect-function --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/sysroots/arm-linux-gnueabihf --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/libc --enable-checking=release --disable-bootstrap --enable-languages=c,c++,fortran,lto --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release/builder_arch/amd64/label/tcwg-x86_64-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu
线程模型:posix
gcc 版本 6.3.1 20170404 (Linaro GCC 6.3-2017.05)
目前大部分 ARM 开发者使用的都是由 Linaro 组织提供的交叉编译器,包括前面使用 APT 安装的 ARM-GCC 工具链,它的来源也是 Linaro。Linaro 是由 ARM 发起,与其它 ARM SOC 公司共同投资的非盈利组织。
arch [-os] [-(gnu)eabi(hf)] -gcc
字段 | 含义 |
---|---|
arch | 目标芯片架构 |
os | 操作系统 |
gnu | C标准库类型 |
eabi | 应用二进制接口 |
hf | 浮点模式 |
以安装的 arm-linux-gnueabihf-gcc 编译器为例。
而另一种名为 arm-linux-gnueabi- gcc 的编译器与它的差别就在于是否带 “hf”,不带 “hf” 表示它使用 soft-float 模式。
总体上整个程序的编译执行过程,可以按图片从上到下的顺序,分为四大部分的内容:
#Makefile格式 #目标:依赖的文件或其它目标 #Tab 命令1 #Tab 命令2 #第一个目标,是最终目标及make的默认目标 #目标a,依赖于目标targetc和targetb #目标要执行的shell命令 ls -lh,列出目录下的内容 targeta: targetc targetb ls -lh echo "Hello World!" #目标b,无依赖 #目标要执行的shell命令,使用touch创建test.txt文件 targetb: touch test.txt #目标c,无依赖 #目标要执行的shell命令,pwd显示当前路径 targetc: pwd #目标d,无依赖 #由于abc目标都不依赖于目标d,所以直接make时目标d不会被执行 #可以使用make targetd命令执行 targetd: rm -f test.txt
这个Makefile文件主要是定义了四个目标 操作,先大致了解它们的关系:
xiechen@xiechen-Ubuntu:~/2.测试中心/1.makefile$ make
pwd
/home/xiechen/2.测试中心/1.makefile
touch test.txt
ls -lh
总用量 4.0K
-rw-r--r-- 1 xiechen xiechen 673 8月 5 17:49 makefile
-rw-r--r-- 1 xiechen xiechen 0 8月 5 17:50 test.txt
echo "Hello World!"
Hello World!
xiechen@xiechen-Ubuntu:~/2.测试中心/1.makefile$ make targetd
rm -f test.txt
由于 targetd 不是默认目标,且不被其它任何目标依赖,所以直接 make 的时候 targetd 并没有被执行,想要单独执行 Makefile 中的某个目标,可以使用 ”make 目标名“ 的语法。
[目标1]:[依赖]
[命令1]
[命令2]
[目标2]:[依赖]
[命令1]
[命令2]
前面我们在Makefile中编写的目标,在make看来其实都是目标文件,例如make在执行 的时候由于在目录找不到targeta文件,所以每次make targeta的时候,它都会去执行targeta的命令,期待执行后能得到名为targeta的 同名文件。如果目录下真的有targeta、targetb、targetc的文件,即假如目标文件和依 赖文件都存在且是最新的,那么make targeta就不会被正常执行了,这会引起误会。
为了避免这种情况,Makefile使用“.PHONY”前缀来区分目标代号和目标文件,并且这种目 标代号被称为“伪目标”,phony单词翻译过来本身就是假的意思。
#默认目标
#hello_main依赖于hello_main.c和hello_func.c文件
hello_main: hello_main.c hello_func.c
gcc -o hello_main hello_main.c hello_func.c -I .
#clean伪目标,用来删除编译生成的文件
.PHONY:clean
clean:
rm -f *.o hello_main
如果以上代码中不写“.PHONY:clean”语句,并且在目录下创建一个名为clean的文件,那么当 执行“make clean”时,clean的命令并不会被执行。
hello_main 目标文件本质上并不是依赖 hello_main.c 和 hello_func.c 文件,而是依赖于 hello_main.o 和 hello_func.o,把这两个文件链接起来就能得到我们最终想要的 hello_main 目标文件。另外,由于 make 有一条默认规则,当找不到 xxx.o 文件时,会查找目录下的同名 xxx.c 文件进行编译。
从 make 的输出可看到,它先执行了两条额外的 “cc” 编译命令,这是由 make 默认规则执行的,它们把 C 代码编译生成了同名的 .o 文件,然后 make 根据 Makefile 的命令链接这两个文件得到最终目标文件 hello_main。
使用 C 自动编译成 *.o 的默认规则有个缺陷,由于没有显式地表示 *.o 依赖于 .h 头文件,假如我们修改了头文件的内容,那么 *.o 并不会更新,这是不可接受的。并且默认规则使用固定的 “cc” 进行编译,假如我们想使用 ARM-GCC 进行交叉编译,那么系统默 认的 “cc” 会导致编译错误。
#定义变量 CC=gcc CFLAGS=-I. DEPS = hello_func.h #目标文件 hello_main: hello_main.o hello_func.o $(CC) -o hello_main hello_main.o hello_func.o #*.o文件的生成规则 %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) #伪目标 .PHONY: clean clean: rm -f *.o hello_main
符号 | 意义 |
---|---|
$@ | 匹配目标文件 |
$% | 与 @ 类 似 , 但 @类似,但 @类似,但%仅匹配“库”类型的目标文件 |
$< | 依赖中的第一个目标文件 |
$^ | 所有的依赖目标,如果依赖中有重复的,只保留一份 |
$+ | 所有的依赖目标,即使依赖中有重复的也原样保留 |
$? | 所有比目标要新的依赖目标 |
#定义变量 #ARCH默认为x86,使用gcc编译器, #否则使用arm编译器 ARCH ?= x86 TARGET = hello_main CFLAGS = -I. DEPS = hello_func.h OBJS = hello_main.o hello_func.o #根据输入的ARCH变量来选择编译器 #ARCH=x86,使用gcc #ARCH=arm,使用arm-gcc ifeq ($(ARCH),x86) CC = gcc else CC = arm-linux-gnueabihf-gcc endif #目标文件 $(TARGET): $(OBJS) $(CC) -o $@ $^ $(CFLAGS) #*.o文件的生成规则 %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) #伪目标 .PHONY: clean clean: rm -f *.o hello_main
make ARCH=arm
#定义变量 #ARCH默认为x86,使用gcc编译器, #否则使用arm编译器 ARCH ?= x86 TARGET = hello_main #存放中间文件的路径 BUILD_DIR = build_$(ARCH) #存放源文件的文件夹 SRC_DIR = sources #存放头文件的文件夹 INC_DIR = includes . #源文件 SRCS = $(wildcard $(SRC_DIR)/*.c) #目标文件(*.o) OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SRCS))) #头文件 DEPS = $(wildcard $(INC_DIR)/*.h) #指定头文件的路径 CFLAGS = $(patsubst %, -I%, $(INC_DIR)) #根据输入的ARCH变量来选择编译器 #ARCH=x86,使用gcc #ARCH=arm,使用arm-gcc ifeq ($(ARCH),x86) CC = gcc else CC = arm-linux-gnueabihf-gcc endif #目标文件 $(BUILD_DIR)/$(TARGET): $(OBJS) $(CC) -o $@ $^ $(CFLAGS) #*.o文件的生成规则 $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(DEPS) #创建一个编译目录,用于存放过程文件 #命令前带“@”,表示不在终端上输出 @mkdir -p $(BUILD_DIR) $(CC) -c -o $@ $< $(CFLAGS) #伪目标 .PHONY: clean cleanall #按架构删除 clean: rm -rf $(BUILD_DIR) #全部删除 cleanall: rm -rf build_x86 build_arm
Linux内核本身也支持FAT32文件系统,而使用NTFS格式则需要安装额外的工具如ntfs-3g。所以使用开发板出厂的 默认Linux系统时,把FAT32格式的U盘直接插入到开发板是可以自动挂载的,而NTFS格式的则不支持。主机上 的Ubuntu对于NTFS或FAT32的U盘都能自动识别并挂载,因为U buntu发行版安装了相应的支持。目前微软已公开exFAT文件系统的标准,且已把它开源至Linux,未来Linux可能 也默认支持exFAT。
对于非常在意FLASH存储器损耗的场合,则可以考虑使用jffs2或yaffs2等文件系统。
xiechen@xiechen-Ubuntu:~/2.测试中心/1.makefile$ df -T 文件系统 类型 1K-块 已用 可用 已用% 挂载点 udev devtmpfs 5573684 0 5573684 0% /dev tmpfs tmpfs 1119520 1396 1118124 1% /run /dev/sda1 ext4 102685624 23871424 73555040 25% / tmpfs tmpfs 5597584 97436 5500148 2% /dev/shm tmpfs tmpfs 5120 4 5116 1% /run/lock tmpfs tmpfs 5597584 0 5597584 0% /sys/fs/cgroup /dev/loop1 squashfs 261760 261760 0 100% /snap/gnome-3-34-1804/36 /dev/loop3 squashfs 384 384 0 100% /snap/gnome-characters/539 /dev/loop0 squashfs 98944 98944 0 100% /snap/core/9436 /dev/loop6 squashfs 1024 1024 0 100% /snap/gnome-logs/100 /dev/loop4 squashfs 2304 2304 0 100% /snap/gnome-system-monitor/148 /dev/loop2 squashfs 99328 99328 0 100% /snap/core/9665 /dev/loop11 squashfs 384 384 0 100% /snap/gnome-characters/550 /dev/loop8 squashfs 56320 56320 0 100% /snap/core18/1754 /dev/loop13 squashfs 2304 2304 0 100% /snap/gnome-calculator/260 /dev/loop9 squashfs 144128 144128 0 100% /snap/gnome-3-26-1604/100 /dev/loop15 squashfs 261760 261760 0 100% /snap/gnome-3-34-1804/33 /dev/loop5 squashfs 63616 63616 0 100% /snap/gtk-common-themes/1506 /dev/loop12 squashfs 2560 2560 0 100% /snap/gnome-calculator/748 /dev/loop16 squashfs 2304 2304 0 100% /snap/gnome-system-monitor/145 /dev/loop14 squashfs 144128 144128 0 100% /snap/gnome-3-26-1604/98 /dev/loop10 squashfs 35456 35456 0 100% /snap/gtk-common-themes/818 /dev/loop7 squashfs 56320 56320 0 100% /snap/core18/1880 /dev/loop17 squashfs 14976 14976 0 100% /snap/gnome-logs/45 Desktop vboxsf 249427964 60868888 188559076 25% /media/sf_Desktop tmpfs tmpfs 1119516 28 1119488 1% /run/user/121 tmpfs tmpfs 1119516 12 1119504 1% /run/user/1000
Linux内核提供了procfs、sysfs和devfs等伪文件系统。
也被称为进程文件系统,procfs通常会自动挂载在根 目录下的/proc文件夹。procfs为用户提供内核状态和进程信息的接口,功能相 当于Windows的任务管理器。
伪文件系统存在于内存中,通常不占用硬盘空间,它以文 件的形式,向用户提供了访问系统内核数据的接口。用户和应用程序 可以通过访问这些数据接口,得到系统的信息,而且内核允许用户修改内核的某些参数。
cat /proc/cpuinfo # 查看CPU信息
ls /proc # 查看proc目录
也被称为进程文件系统,procfs通常会自动挂载在根目录下的/proc文件夹。procfs为用户提供内核状态和进程信息的接口,功能相当于Windows的任务管理器。
文件名 | 作用 |
---|---|
pid* | “pid*”通常就是一个数字,该数字表示的是进程的 PID 号,系统中当前运行的每一个进程都有对应的一个目录,用于记录进程所有相关信息。对于操作系统来说,一个应用程序就是一个进程 |
self | 该文件是一个软链接,指向了当前进程的目录,通过访问/proc/self/目录来获取当前进程的信息,就不用每次都获取pid |
thread-self | 该文件也是一个软链接,指向了当前线程,访问该文件,等价于访问“当前进程pid/task/当前线程tid”的内容。一个进程,可以包含多个线程,但至少需要一个进程,这些线程共同支撑进程的运行。 |
version | 记录了当前运行的内核版本,通常可以使用“uname –r”命令查看 |
cpuinfo | 记录系统中CPU的提供商和相关配置信息 |
modules | 记录了目前系统加载的模块信息 |
meminfo | 记录系统中内存的使用情况,free命令会访问该文件,来获取系统内存的空闲和已使用的数量 |
filesystems | 记录内核支持的文件系统类型,通常mount一个设备时,如果没有指定文件系统并且它无法确定文件系统类型时,mount会尝试包含在该文件中的文件系统,除了那些标有“nodev”的文件系统。 |
ps # 查看当前进程的 PID 号; xiechen@xiechen-Ubuntu:~/2.测试中心/1.makefile$ ps PID TTY TIME CMD 28830 pts/2 00:00:00 bash 28843 pts/2 00:00:00 ra 28880 pts/2 00:00:00 bash 32410 pts/2 00:00:00 ps xiechen@xiechen-Ubuntu:~/2.测试中心/1.makefile$ ls /proc/28830 arch_status cpuset loginuid numa_maps sched status attr cwd map_files oom_adj schedstat syscall autogroup environ maps oom_score sessionid task auxv exe mem oom_score_adj setgroups timers cgroup fd mountinfo pagemap smaps timerslack_ns clear_refs fdinfo mounts patch_state smaps_rollup uid_map cmdline gid_map mountstats personality stack wchan comm io net projid_map stat coredump_filter limits ns root statm
文件名 | 文件内容 |
---|---|
cmdline | 只读文件,记录了该进程的命令行信息,如命令以及命令参数 |
comm | 记录了进程的名字 |
environ | 进程使用的环境变量 |
exe | 软连接文件,记录命令存放的绝对路径 |
fd | 记录进程打开文件的情况,以文件描述符作为目录名 |
fdinfo | 记录进程打开文件的相关信息,包含访问权限以及挂载点,由其文件描述符命名。 |
io | 记录进程读取和写入情况 |
map_files | 记录了内存中文件的映射情况,以对应内存区域起始和结束地址命名 |
maps | 记录当前映射的内存区域,其访问权限以及文件路径。 |
stack | 记录当前进程的内核调用栈信息 |
status | 记录进程的状态信息 |
syscall | 显示当前进程正在执行的系统调用。第一列记录了系统调用号 |
task | 记录了该进程的线程信息 |
wchan | 记录当前进程处于睡眠状态,内核调用的相关函数 |
Linux内核在2.6版本中引入了sysfs文件系统,sysfs通常会自动挂载在根目录下的sys文件夹。sys目录下的文 件/文件夹向用户提供了一些关于设备、内核模块、文件系统以及其他内核组件的信息,如子目录block中存放了所 有的块设备,而bus中存放了系统中所有的总线类型,有i2c,usb,sdi o,pci等。下图中的虚线表示软连接,可以看到所有跟设备 有关的文件或文件夹都链接到了device目录下,类似于将一个大类,根 据某个特征分为了无数个种类,这样使得/sys文件夹的结构层次清晰明了。
文件名 | 作用 |
---|---|
block | 记录所有在系统中注册的块设备,这些文件都是符号链接,都指向了/sys/devices目录。 |
bus | 该目录包含了系统中所有的总线类型,每个文件夹都是以每个总线的类型来进行命名。 |
class | 包含了所有在系统中注册的设备类型,如块设备,声卡,网卡等。文件夹下的文件同样也是一些链接文件,指向了/sys/devices目录。 |
devices | 包含了系统中所有的设备,到跟设备有关的文件/文件夹,最终都会指向该文件夹。 |
module | 该目录记录了系统加载的所有内核模块,每个文件夹名以模块命名 |
fs | 包含了系统中注册文件系统 |
概括来说,sysfs文件系统是内核加载驱动时,根据系统上的设备和总 线构成导出的分级目录,它是系统上设备的直观反应,每个设备在sysfs下都有 唯一的对应目录,用户可以通过具体设备目录下的文件访问设备。
在Linux 2.6内核之前一直使用的是devfs文件系统管理设备,它通 常挂载于/dev目录下。devfs中的每个文件都对应一个设备,用户也可以通过/dev目录下的文件访问硬件。在sysfs出现之前,devfs是在制作根文件系统的时候就已经固定的,这不太方便使用,而当代的devfs通常会在系统运行时使用名为udev的工具根据sysfs目录生成devfs目录。
程序(program)是一个普通文件,是为了完成特定任务而准备好的指令序列与数据的集合,这些指令和数据以”可执行映像”的格式保存在磁盘中。正如我们所写的一些代码,经过编译器编译后,就会生成对应的可执行文件,那么这个就是程序,或者称之为可执行程序。
进程(process)则是程序执行的具体实例,比如一个可执行文件,在执行的时候,它就是一个进程,直到该程序执 行完毕。那么在程序执行的过程中,它享有系统的资源,至少包括进程的运行环境、CPU、外设、内存、进程ID等资源与 信息,同样的一个程序,可以实例化为多个进程,在Linux系统下使用 ps命令可以查看到当前正在执行的进程,当这个可执行程序运行完毕后,进程也会随之被销毁(可能不是立即销毁,但是总 会被销毁)。
程序并不能单独执行,只有将程序加载到内存中,系统为他分配资源后才能够执行,这种执行的程序称之为进程,也就是说进程 是系统进行资源分配和调度的一个独立单位,每个进程都有自己单独的地址空间。
在Linux系统中,程序只是个静态的文件,而进程是一个动态的实体。
其实正如我们运行一个程序(可执行文件),通常在 Shell中输入命令运行就可以了,在这运行的过程中包含了程序到进 程转换的过程,整个转换过程主要包含以下 3 个步骤:
查找命令对应程序文件的位置。
使用 fork()函数为启动一个新进程。
在新进程中调用 exec 族函数装载程序文件,并执行程序文件中的main()函数。
程序只是一系列指令序列与数据的集合,它本身没有任何运行的含义,它只是一个静态 的实体。而进程则不同,它是程序在某个数据集上的执行过程,它是一个动态运行的实体,有自己的生命周期,它因启动而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被销毁。
进程和程序并不是一一对应的,一个程序执行在不同的数据集上运行就会成为不同的进程,可以用进程控制块来唯一地标识系统中的每个进程。而这一点正是程序无法做到的,由于程序没有和数据产生直接的联系,既使是执行不同的数据的程序,他们的指令的集合依然是一样的,所以无法唯一地标识出这些运行于不同数据集上的程序。一般来说,一个进程肯定有一个与之对应的程序,而且有且只有一个。而一个程序有可能没有与之对应的进程(因为这个程序没有被运行),也有可能有多个进程与之对应(这个程序可能运行在多个不同的数据集上)。
进程具有并发性而程序没有。
进程是竞争计算机资源的基本单位,而程序不是。
xiechen@xiechen-Ubuntu:/sys/class/leds/input2::scrolllock$ ps -ux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND xiechen 2711 0.0 0.0 77056 8140 ? Ss 09:13 0:00 /lib/systemd/systemd --user xiechen 2712 0.0 0.0 261596 2716 ? S 09:13 0:00 (sd-pam) xiechen 2729 0.0 0.0 206892 5960 tty2 Ssl+ 09:13 0:00 /usr/lib/gdm3/gdm-x-session --run-script i3 xiechen 2731 0.7 0.9 999072 105148 tty2 Sl+ 09:13 0:33 /usr/lib/xorg/Xorg vt2 -displayfd 3 -auth /run/user/1000/gdm/Xauthority -background none -noreset -keeptty -verbose 3 xiechen 2744 0.0 0.0 50048 4360 ? Ss 09:13 0:00 /usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only xiechen 2747 0.0 0.1 139656 15124 tty2 S+ 09:13 0:00 i3 xiechen 2773 0.0 0.7 432676 80544 ? Sl 09:13 0:01 /usr/bin/fcitx xiechen 2820 0.0 0.0 49936 3660 ? Ss 09:13 0:00 /usr/bin/dbus-daemon --syslog --fork --print-pid 5 --print-address 7 --config-file /usr/share/fcitx/dbus/daemon.conf xiechen 2827 0.0 0.0 27592 208 ? SN 09:13 0:00 /usr/bin/fcitx-dbus-watcher unix:abstract=/tmp/dbus-0rFOEgsRrC,guid=39d552ae67d9a5d9511fc9415f2b592c 2820 xiechen 2843 0.0 0.0 54372 372 ? S 09:13 0:00 /usr/bin/VBoxClient --clipboard xiechen 2845 0.0 0.0 188804 4464 ? Sl 09:13 0:00 /usr/bin/VBoxClient --clipboard xiechen 2851 0.0 0.0 54372 372 ? S 09:13 0:00 /usr/bin/VBoxClient --display xiechen 2852 0.0 0.0 54504 2908 ? S 09:13 0:00 /usr/bin/VBoxClient --display xiechen 2855 0.0 0.0 54372 372 ? S 09:13 0:00 /usr/bin/VBoxClient --seamless xiechen 2857 0.0 0.0 186608 2784 ? Sl 09:13 0:00 /usr/bin/VBoxClient --seamless xiechen 2860 0.0 0.0 54372 372 ? S 09:13 0:00 /usr/bin/VBoxClient --draganddrop xiechen 2862 0.0 0.0 187124 2824 ? Sl 09:13 0:03 /usr/bin/VBoxClient --draganddrop xiechen 2876 0.0 0.0 11300 320 ? Ss 09:13 0:00 /usr/bin/ssh-agent /usr/bin/sogou-session /usr/bin/im-launch i3 xiechen 2885 0.0 0.0 405572 9148 ? Ssl 09:13 0:00 /usr/bin/dunst xiechen 2894 0.0 0.0 4624 772 ? S 09:13 0:00 /bin/sh -c i3bar --bar_id=bar-0 --socket="/run/user/1000/i3/ipc-socket.2747" xiechen 2895 0.0 0.0 96648 9940 ? S 09:13 0:00 i3bar --bar_id=bar-0 --socket=/run/user/1000/i3/ipc-socket.2747 xiechen 2896 0.0 0.0 4624 808 ? S 09:13 0:00 /bin/sh -c /home/xiechen/contrib/net-speed.sh xiechen 2897 0.0 0.0 4624 860 ? S 09:13 0:00 /bin/sh /home/xiechen/contrib/net-speed.sh xiechen 2901 0.0 0.0 79468 5168 ? S 09:13 0:00 i3status xiechen 2902 0.0 0.0 4624 1176 ? S 09:13 0:00 /bin/sh /home/xiechen/contrib/net-speed.sh xiechen 2910 0.0 0.1 153660 18016 ? S 09:13 0:00 sogou-qimpanel-watchdog xiechen 2943 0.0 0.0 0 0 ? Z 09:13 0:00 [sh] <defunct> xiechen 3011 0.0 0.0 349300 6460 ? Ssl 09:13 0:00 /usr/lib/at-spi2-core/at-spi-bus-launcher xiechen 3016 0.0 0.0 49792 3832 ? S 09:13 0:00 /usr/bin/dbus-daemon --config-file=/usr/share/defaults/at-spi2/accessibility.conf --nofork --print-address 3 xiechen 3020 0.0 0.0 220780 6800 ? Sl 09:13 0:00 /usr/lib/at-spi2-core/at-spi2-registryd --use-gnome-session xiechen 3023 0.0 0.5 690624 57172 ? Dsl 09:13 0:02 /usr/lib/gnome-terminal/gnome-terminal-server xiechen 3027 0.0 0.0 286752 7076 ? Ssl 09:13 0:00 /usr/lib/gvfs/gvfsd xiechen 3032 0.0 0.0 350576 5408 ? Sl 09:13 0:00 /usr/lib/gvfs/gvfsd-fuse /run/user/1000/gvfs -f -o big_writes xiechen 3041 0.0 0.0 610188 10128 ? Ssl 09:13 0:00 /usr/libexec/xdg-desktop-portal xiechen 3046 0.0 0.0 424736 6012 ? Ssl 09:13 0:00 /usr/libexec/xdg-document-portal xiechen 3049 0.0 0.0 266280 4968 ? Ssl 09:13 0:00 /usr/libexec/xdg-permission-store xiechen 3060 0.0 0.1 534500 15752 ? Ssl 09:13 0:00 /usr/libexec/xdg-desktop-portal-gtk xiechen 3068 0.0 0.0 24608 5212 pts/0 Ss 09:13 0:00 bash xiechen 3252 0.0 0.2 74708 22632 pts/0 S+ 09:14 0:00 /usr/bin/python -O /usr/bin/ra xiechen 3406 0.0 0.0 4624 808 pts/0 S+ 09:14 0:00 /bin/sh -c set -- '/home/xiechen/0.xcCore/1.hobbies/10.深入学习野火开发板.md'; $EDITOR -- "$@" xiechen 3407 0.0 0.4 1745704 46996 pts/0 Sl+ 09:14 0:02 vim -- /home/xiechen/0.xcCore/1.hobbies/10.深入学习野火开发板.md xiechen 3413 0.0 0.4 2476596 51208 ? Ssl 09:14 0:00 /usr/bin/python3 /home/xiechen/.vim/plugged/YouCompleteMe/python/ycm/../../third_party/ycmd/ycmd --port=43933 --options_file=/ xiechen 3490 0.0 0.0 4624 804 ? S 09:14 0:00 /bin/sh -c google-chrome xiechen 3491 1.1 1.6 919120 188348 ? Sl 09:14 0:51 /opt/google/chrome/chrome xiechen 3496 0.0 0.0 9356 840 ? S 09:14 0:00 cat xiechen 3497 0.0 0.0 9356 740 ? S 09:14 0:00 cat xiechen 3500 0.0 0.5 461304 60820 ? S 09:14 0:00 /opt/google/chrome/chrome --type=zygote --no-zygote-sandbox xiechen 3503 0.0 0.5 461336 60592 ? S 09:14 0:00 /opt/google/chrome/chrome --type=zygote xiechen 3504 0.0 0.0 27348 3052 ? S 09:14 0:00 /opt/google/chrome/nacl_helper xiechen 3505 0.0 0.0 27348 4132 ? S 09:14 0:00 /opt/google/chrome/nacl_helper xiechen 3508 0.0 0.1 461336 15684 ? S 09:14 0:00 /opt/google/chrome/chrome --type=zygote xiechen 3526 0.0 0.9 3355576 107068 ? Sl 09:14 0:01 sogou-qimpanel xiechen 3577 0.0 0.7 519392 79040 ? Sl 09:14 0:01 /opt/google/chrome/chrome --type=utility --utility-sub-type=network.mojom.NetworkService --field-trial-handle=7644249088015697 xiechen 3598 1.0 0.8 577000 93492 ? Sl 09:14 0:46 /opt/google/chrome/chrome --type=gpu-process --field-trial-handle=7644249088015697058,16091983410253672671,131072 --gpu-prefer xiechen 3601 0.0 0.1 477696 16136 ? S 09:14 0:00 /opt/google/chrome/chrome --type=broker xiechen 3625 2.0 2.0 4961204 235028 ? Sl 09:14 1:33 /opt/google/chrome/chrome --type=renderer --field-trial-handle=7644249088015697058,16091983410253672671,131072 --disable-gpu-c xiechen 5268 0.1 1.5 4909464 172900 ? Sl 09:35 0:03 /opt/google/chrome/chrome --type=renderer --field-trial-handle=7644249088015697058,16091983410253672671,131072 --disable-gpu-c xiechen 5283 0.0 0.4 4750016 54704 ? Sl 09:35 0:00 /opt/google/chrome/chrome --type=renderer --field-trial-handle=7644249088015697058,16091983410253672671,131072 --disable-gpu-c xiechen 6253 0.0 0.0 24872 5840 pts/1 Ss 09:57 0:00 bash xiechen 8025 0.0 0.0 41420 3556 pts/1 R+ 10:29 0:00 ps -ux
状态 | 说明 |
---|---|
R | 运行状态。严格来说,应该是”可运行状态”,即表示进程在运行队列中,处于正在执行或即将运行状态,只有在该状态的进程才可能在 CPU 上运行,而同一时刻可能有多个进程处于可运行状态。 |
S | 可中断的睡眠状态。处于这个状态的进程因为等待某种事件的发生而被挂起,比如进程在等待信号。 |
D | 不可中断的睡眠状态。通常是在等待输入或输出(I/O)完成,处于这种状态的进程不能响应异步信号。 |
T | 停止状态。通常是被shell的工作信号控制,或因为它被追踪,进程正处于调试器的控制之下。 |
Z | 退出状态。进程成为僵尸进程。 |
X | 退出状态。进程即将被回收。 |
s | 进程是会话其首进程。 |
l | 进程是多线程的。 |
进程属于前台进程组。 | 进程属于前台进程组。 |
< | 高优先级任务。 |
这个system ()函数是C标准库中提供的,它主要是提供了一种调用其它程序的简单方法。读者可以利用system()函数调用一些应用程序,它产生的结果与从 shell中执行这个程序基本相似。事实上,system()启动了一个运行着/bin/sh的子进程,然后将命令交由它执行。
fork()函数用于从一个已存在的进程中启动一个新进程,新进程称为子进程,而原进程称为父进程。
因为子进程几乎是父进程的完全复制,所以父子两个进程会运行同一个程序,但是这种复制有一个很大的问题,那就是资源与时间都会消耗很大,当发出fork()系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。
创建一个地址空间的这种方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存中的内容,因此直接复制物理内存对系统的开销会产生很大的影响,更重要的是在大多数情况下,这样直接拷贝通常是毫无意义的,因为许多子进程通过装入一个新的程序开始它们的执行,这样就完全丢弃了所继承的地址空间。因此在Linux中引入一种写时复制技术(Copy On Write,简称COW),我们知道,Linux系统中的进程都是使用虚拟内存地址,虚拟地址与真实物理地址之间是有一个对应关系的,每个进程都有自己的虚拟地址空间,而操作虚拟地址明显比直接操作物理内存更加简便快捷,那么显而易见的,写时复制是一种可以推迟甚至避免复制数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间(页面)。
那么写时复制的思想就是在于:父进程和子进程共享页面而不是复制页面。而共享页面就不能被修改,无论父进程和子进程何时试图向一个共享的页面写入内容时,都会产生一个错误,这时内核就把这个页复制到一个新的页面中并标记为可写。原来的页面仍然是写保护的,当还有进程试图写入时,内核检查写进程是否是这个页面的唯一属主,如果是则把这个页面标记为对这个进程是可写的。
总的来说,写时复制只会用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间,资源的复制是在需要写入的时候才会进行,在此之前,父进程与子进程都是以只读方式共享页面,这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。而在绝大多数的时候共享的页面根本不会被写入,例如,在调用fork()函数后立即执行exec(),地址空间就无需被复制了,这样一来fork()的实际开销就是复制父进程的页表以及给子进程创建一个进程描述符。
在fork()启动新的进程后,子进程与父进程开始并发执行,谁先执行由内核调度算法来决定。fork()函数如果成功启动了进程,会对父子进程各返回一次,其中对父进程返回子进程的 PID,对子进程返回0;如果fork()函数启动子进程失败,它将返回-1。失败通常是因为父进程所拥有的子进程数目超过了规定的限制(CHILD_MAX),此时errno将被设为EAGAIN。如果是因为进程表里没有足够的空间用于创建新的表单或虚拟内存不足,errno变量将被设为ENOMEM。
这个系列函数主要是用于替换进程的执行程序,它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。简单来说就是覆盖进程。
_exit()函数的作用最为简单:直接通过系统调用使进程终止运行,当然,在终止进程的时候会清除这个进程使用的内存空间,并销毁它在内核中的各种数据结构;而exit()函数则在这些基础上做了一些包装,在执行退出之前加了若干道工序:比如exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,这就是”清除I/O缓冲”。
由于在 Linux 的标准函数库中,有一种被称作”缓冲 I/O(buffered I/O)”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足了一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也为编程带来了一些麻烦。比如有些数据,认为已经被写入文件中,实际上因为没有满足特定的条件,它们还只是被保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区中的数据就会丢失。因此,若想保证数据的完整性,就一定要使用 exit()函数。
通过在父进程中调用wait()或者waitpid()函数让父进程等待子进程的结束。
当一个进程调用了exit()之后,该进程并不会立刻完全消失,而是变成了一个僵尸进程。僵尸进程是一种非常特殊的进程,它已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。那么无论如何,父进程都要回收这个僵尸进程,因此调用wait()或者waitpid()函数其实就是将这些僵尸进程回收,释放僵尸进程占有的内存空间,并且了解一下进程终止的状态信息。
wait()函数在被调用的时候,系统将暂停父进程的执行,直到有信号来到或子进程结束,如果在调用wait()函数时子进程已经结束,则会立即返回子进程结束状态值。子进程的结束状态信息会由参数wstatus返回,与此同时该函数会返子进程的PID,它通常是已经结束运行的子进程的PID。状态信息允许父进程了解子进程的退出状态,如果不在意子进程的结束状态信息,则参数wstatus可以设成NULL。
wait()函数有几点需要注意的地方:
当然,除此之外,Linux系统中还提供关于等待子进程退出的一些宏定义,我们可以使用这些宏定义来直接判断子进程退出的状态:
waitpid()函数 的作用和wait()函数一样,但它并不一定要等待第一个终止的子进程,它还有其他选项,比如指定等待某个pid的子进程、提供一个非阻塞版本的wait()功能等。实际上 wait()函数只是 waitpid() 函数的一个特例,在 Linux内部实现 wait 函数时直接调用的就是 waitpid 函数。
一个进程产生数据,通过管道发送给另一个进程,另一个进程读取到数据,这样一来就实现了进程间的通信了。
xiechen@xiechen-Ubuntu:/sys/class/leds/input2::scrolllock$ ps -aux | grep root
root 1 0.0 0.0 225524 9212 ? Ss 09:12 0:03 /sbin/init splash
root 2 0.0 0.0 0 0 ? S 09:12 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 09:12 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 09:12 0:00 [rcu_par_gp]
root 6 0.0 0.0 0 0 ? I< 09:12 0:00 [kworker/0:0H-kb]
ps 列出当前的进程,grep 是一种强大的文本搜索工具,| 是一个管道。将ps命令输出的数据通过管道流向grep,其实在这里就打开了两个进程,ps命令本应该在终端输出信息的,但是它通过管道将输出的信息作为grep命令的输入信息,然后通过搜索之后将合适的信息显示出来,这样子就形成了我们在终端看到的信息。
xiechen@xiechen-Ubuntu:/sys/class/leds/input2::scrolllock$ ps -ux | grep $USER
xiechen 10578 0.0 0.0 41420 3708 pts/1 R+ 11:19 0:00 ps -ux
xiechen 10579 0.0 0.0 16176 1088 pts/1 S+ 11:19 0:00 grep --color=auto xiechen
管道本质上是一个文件,可以看做是ps进程将输出的内容写入管道中,grep进程从管道中读取数据,这样子就是一个可读可写的文件,这其实也遵循了Linux中”一切皆文件”的设计思想,因此Linux系统直接把管道实现成了一种文件系统,借助VFS给应用程序提供操作接口。不过还是要注意的是:虽然管道的实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间,它占用的是内存空间,因此Linux上的管道就是一个操作方式为文件的内存缓冲区。
Linux系统上的管道分两种类型:
匿名管道最常见的形态就是我们在shell操作中最常用的”|”。它的特点是只能在父子进程中使用,父进程在产生子进程前必须打开一个管道文件,然后fork产生子进程,这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到使用同一个管道通信的目的。此时除了父子进程外,没人知道这个管道文件的描述符,所以通过这个管道中的信息无法传递给其他进程。这保证了传输数据的安全性,当然也降低了管道了通用性。
系统还提供了命名管道,它本质是一个文件,位于文件系统中,命名管道可以让多个无相关的进程进行通讯。
匿名管道(PIPE)是一种特殊的文件,但虽然它是一种文件,却没有名字,因此一般进程无法使用open()来获取他的描述符,它只能在一个进程中被创建出来,然后通过继承的方式将他的文件描述符传递给子进程,这就是为什么匿名管道只能用于亲缘关系进程间通信的原因。
匿名管道不同于一般文件的显著之处是:它有两个文件描述符,而不是一个,一个只能用来读,另一个只能用来写,这就是所谓的”半双工”通信方式。而且它对写操作不做任何保护,即:假如有多个进程或线程同时对匿名管道进行写操作,那么这些数据很有可能会相互践踏,因此一个简单的结论是:匿名管道只能用于一对一的亲缘进程通信。
匿名管道不能使用lseek()来进行所谓的定位,因为他们的数据不像普通文件那样按块的方式存放在诸如硬盘、flash 等块设备上。
总结来说,匿名管道有以下的特征:
命名管道(FIFO)与匿名管道(PIPE)是不同的,命名管道可以在多个无关的进程中交换数据(通信)。
命名管道不同于无名管道之处在于它提供了一个路径名与之关联,以一个文件形式存在于文件系统中。
通过文件的形式,那么就可以调用系统中对文件的操作,如打开(open)、读(read)、写(write)、关闭(close)等函数。
虽然命名管道文件存储在文件系统中,但数据却是存在于内存中的,这点要区分开。
总结来说,命名管道有以下的特征:
pipe()函数用于创建一个匿名管道,一个可用于进程间通信的单向数据通道。
想在不相关的进程之间交换数据,这还不是很方便,我们可以用FIFO文件来完成这项工作,或者称之为命名管道。
一个进程对管道进行读操作时:
一个进程对管道进行写操作时:
信号(signal),又称为软中断信号,用于通知进程发生了异步事件,它是Linux系统响应某些条件而产生的一个事件,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。正如我们所了解的中断服务函数一样,在中断发生的时候,就会进入中断服务函数中去处理,同样的,当进程接收到一个信号的时候,也会相应地采取一些行动。
使用术语“生成(raise)”表示一个信号的产生,使用术语“捕获(catch)”表示进程接收到一个信号。
在Linux系统中,信号可能是由于系统中某些错误而产生,也可以是某个进程主动生成的一个信号。
由于某些错误条件而生成的信号:如内存段冲突、浮点处理器错误或非法指令等,它们由shell和终端处理器生成并且引起中断。
使用kill 命令来查看系统中支持的信号种类。
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
Linux系统支持信号62种信号,每种信号名称都以SIG三个字符开头,注意,没有信号值为32和33的信号。
可以将这62中信号分为2大类:
信号值 | 名称 | 描述 | 默认处理 |
---|---|---|---|
1 | SIGHUP | 控制终端被关闭时产生。 | 终止 |
2 | SIGINT | 程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl + C)时发出,用于通知前台进程组终止进程。 | 终止 |
3 | SIGQUIT | SIGQUIT 和SIGINT类似,但由QUIT字符(通常是Ctrl + )来控制,进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。 | 终止并产生转储文件(core文件) |
4 | SIGILL | CPU检测到某进程执行了非法指令时产生,通常是因为可执行文件本身出现错误, 或者试图执行数据段、堆栈溢出时也有可能产生这个信号。 | 终止并产生转储文件(core文件) |
5 | SIGTRAP | 由断点指令或其它trap指令产生,由debugger使用。 | 终止并产生转储文件(core文件) |
6 | SIGABRT | 调用系统函数 abort()时产生。 | 终止并产生转储文件(core文件) |
7 | SIGBUS | 总线错误时产生。一般是非法地址,包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。 | 终止并产生转储文件(core文件) |
8 | SIGFPE | 处理器出现致命的算术运算错误时产生,不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。 | 终止并产生转储文件(core文件) |
9 | SIGKILL | 系统杀戮信号。用来立即结束程序的运行,本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号将进程杀死。 | 终止 |
10 | SIGUSR1 | 用户自定义信号。 | 终止 |
11 | SIGSEGV | 访问非法内存时产生,进程试图访问未分配给自己的内存,或试图往没有写权限的内存地址写数据。 | 终止 |
12 | SIGUSR2 | 用户自定义信号。 | 终止 |
13 | SIGPIPE | 这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止,也会产生这个信号。 | 终止 |
14 | SIGALRM | 定时器到期信号,计算的是实际的时间或时钟时间,alarm函数使用该信号。 | 终止 |
15 | SIGTERM | 程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号,如果进程终止不了,才会尝试SIGKILL。 | 终止 |
16 | SIGSTKFLT | 已废弃。 | 终止 |
17 | SIGCHLD | 子进程暂停或终止时产生,父进程将收到这个信号,如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程,这种情况我们应该避免。父进程默认是忽略SIGCHILD信号的,我们可以捕捉它,做成异步等待它派生的子进程终止,或者父进程先终止,这时子进程的终止自动由init进程来接管。 | 忽略 |
18 | SIGCONT | 系统恢复运行信号,让一个停止(stopped)的进程继续执行,本信号不能被阻塞,可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作 | 恢复运行 |
19 | SIGSTOP | 系统暂停信号,停止进程的执行。注意它和terminate以及interrupt的区别:该进程还未结束,只是暂停执行,本信号不能被阻塞,处理或忽略。 | 暂停 |
20 | SIGTSTP | 由控制终端发起的暂停信号,停止进程的运行,但该信号可以被处理和忽略,比如用户键入SUSP字符时(通常是Ctrl+Z)发出这个信号。 | 暂停 |
21 | SIGTTIN | 后台进程发起输入请求时控制终端产生该信号。 | 暂停 |
22 | SIGTTOU | 后台进程发起输出请求时控制终端产生该信号。 | 暂停 |
23 | SIGURG | 套接字上出现紧急数据时产生。 | 忽略 |
24 | SIGXCPU | 处理器占用时间超出限制值时产生。 | 终止并产生转储文件(core文件) |
25 | SIGXFSZ | 文件尺寸超出限制值时产生。 | 终止并产生转储文件(core文件) |
26 | SIGVTALRM | 由虚拟定时器产生的虚拟时钟信号,类似于SIGALRM,但是计算的是该进程占用的CPU时间。 | 终止 |
27 | SIGPROF | 类似于SIGALRM / SIGVTALRM,但包括该进程用的CPU时间以及系统调用的时间。 | 终止 |
28 | SIGWINCH | 窗口大小改变时发出。 | 忽略 |
29 | SIGIO | 文件描述符准备就绪, 可以开始进行输入/输出操作。 | 终止 |
30 | SIGPWR | 启动失败时产生。 | 终止 |
31 | SIGUNUSED | 非法的系统调用。 | 31 |
非实时信号,它主要是因为这类信号不支持排队,因此信号可能会丢失。比如发送多次相同的信号,进程只能收到一次,也只会处理一次,因此剩下的信号将被丢弃。
实时信号,它是支持排队的,发送了多少个信号给进程,进程就会处理多少次。
一般来说,一个进程收到一个信号后不会被立即处理,而是在恰当时机进行处理。一般是在中断返回的时候,或者内核态返回用户态的时候 (这种情况是比较常见的处理方式)。
也就是说,即使这些信号到来了,进程也不一定会立即去处理它,因为系统不会为了处理一个信号而把当前正在运行的进程挂起,这样的话系统的资源消耗太大了,如果不是紧急信号,是不会立即处理的,所以系统一般都会选择在内核态切换回用户态的时候处理信号。比如有时候进程处于休眠状态,但是又收到了一个信号,于是系统就得把信号储存在进程唯一的 PCB(进程控制块)当中。
非实时信号则是不支持排队的,假如此时又有一个信号到来,那么它将被丢弃,这样进程就无法处理这个信号,所以它是不可靠的。
对于实时信号则没有这种顾虑,因为它支持排队,信号是不会被丢弃的,这样子每个到来的信号都能得到有效处理。
生成信号的事件一般可以归为3大类。
信号的生成既可以是同步的,也可以是异步的。
同步信号大多数是程序执行过程中出现了某个错误而产生的,由进程显式请求生成的给自己的信号也是同步的。
异步信号是接收进程可控制之外的事件所生成的信号,这类信号一般是进程无法控制的,只能被动接收,因为进程也不知道这个信号会何时发生,只能在发生的时候去处理它。一般外部事件总是异步地生成信号,异步信号可在进程运行中的任意时刻产生,进程无法预期信号到达的时刻,它所能做的只是告诉Linux内核假如有信号生成时应当采取什么行动(这相当于注册信号对应的处理)。
无论是同步还是异步信号,当信号发生时,我们可以告诉Linux内核采取如下3种动作中的任意一种:
kill - SIGHUP 666
kill -1 666 # 这里的-1是指信号值为1 的SIGHUP信号;
raise()函数只是进程向自身发送信号的,而没有向其他进程发送信号,可以说kill(getpid(),sig)等同于raise(sig)。
因为它只往自身发送信号,不存在权限问题,也不存在目标进程不存在的情况。
alarm()也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送SIGALARM信号。
如果在seconds秒内再次调用了alarm()函数设置了新的闹钟,则新的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代,如果新的seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。因此它的返回值是之前闹钟的剩余秒数,如果之前未设闹钟则返回0。
signal()主要是用于捕获信号,可以改变进程中对信号的默认行为,我们在捕获这个信号后,也可以自定义对信号的处理行为,当收到这个信号后,应该如何去处理它。
Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。
前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了”system V IPC“,通信进程局限在单个计算机内(同一个设备的不同进程间通讯)。
后者则跳过了该限制,形成了基于套接字(socket)的进程间通信机制(多用于不同设备的进程间通讯)。
Linux既有”system V IPC”,又支持socket。
消息队列、共享内存 和 信号量 被统称为 system-V IPC,V 是罗马数字 5,是 Unix 的AT&T 分支的其中一个版本,一般习惯称呼他们为 IPC 对象,这些对象的操作接口都比较类似,在系统中他们都使用一种叫做 key 的键值来唯一标识,而且他们都是“持续性”资源——即他们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。
Linux的消息队列(queue)实质上是一个内核地址空间中的内部链表,它有消息队列标识符(queue ID)。通过Linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用消息队列标识符唯一地进行识别。
内核中是通过IPC的标识符来区别不同的消息队列,不同的消息队列之间是相互独立的,每个消息队列中的消息,又构成一个独立的链表,这样子就是在内涵中维护多个消息队列,每个消息队列的内部又通过链表维护了不同的消息。
每个内核中的 IPC 结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符(identifier)加以引用。
用户接触到的只是IPC关键字,并非IPC标识符,这是因为IPC标识符是IPC结构的内部名。为使多个合作进程能够在同一IPC对象上会合,需要提供一个外部名方案,即关键字(key)每一个IPC对象都与一个IPC关键字相关联,于是关键字就作为该IPC结构的外部名。要想获得一个唯一标识符,必须使用一个IPC关键字,这样子只要不同进程间使用的关键字是相同的,就可以得到相同的IPC标识符,这样子就能保证访问到相同的消息队列。因此无论何时创建IPC结构,都应指定一个关键字(key),关键字的数据类型由系统规定为 key_t,是一个长整型的数据类型,关键字由内核变换成标识符。
信号承载的信息量少,而消息队列可以承载大量的数据。
该函数的作用是得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符。
这个函数的主要作用就是将消息写入到消息队列,俗称发送一个消息。
msgrcv()函数是从标识符为msqid的消息队列读取消息并将消息存储到msgp中,读取后把此消息从消息队列中删除,也就是俗话说的接收消息。
消息队列是可以被用户操作的,比如设置或者获取消息队列的相关属性,那么可以通过msgctl()函数去处理它。
本质上是一个计数器,用于多进程间对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得该临界资源在一个时刻只有一个进程独享。
由于信号量只能进行两种操作等待和发送信号,即P操作和V操作,锁行为就是P操作,解锁就是V操作。
PV操作是计算机操作系统需要提供的基本功能之一,它们的行为如下。
在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)。
注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的
功能是创建或者获取一个已经创建的信号量。
对信号量进行PV操作。
对某个进程,在指定SEM_UNDO后,对信号量的当前值的修改都会反应到信号量调整值上,当该进程终止的时候,内核会根据信号量调整值重新恢复信号量之前的值。
获取或者设置信号量的相关属性。
函数主要是对信号量集的一系列控制操作,根据操作命令cmd的不同,执行不同的操作,依赖于所请求的命令,第四个参数是可选的。
内核按照体系结构分为两类:微内核(microkernel)和宏内核(macrokernel)。
Windows操作系统、华为的鸿蒙操作系统就属于微内核结构。微内核是提供操作系统核心功能的内核 的精简版,实现进程管理、存储器管理、进程间通信、I/O设备管理等基本功能,其他功能模块则需要单独进行编译,具有动态扩展性的优点。
Linux操作系统则是采用了宏内核结构。宏内核是将内核所有的功能都编译成一个整体,其优点是执行 效率非常高,缺点也是十分明显的,一旦我们想要修改、增加内核某个功能时,都需要重新编译一遍内核。
为了解决这个问题,Linux引入了内核模块这一机制。
内核模块就是实现了某个功能的一段内核代 码,在内核运行过程,可以加载这部分代码到内核中, 从而动态地增加了内核的功能。基于这种特性,我们进行 设备驱动开发时,以内核模块的形式编写设备驱动,只需要编译相关的驱动代码即可,无需对整个内核进行编译。
命令 | 作用 |
---|---|
lsmod | 用于显示所有已载入系统的内核模块。 |
insmod | 用于加载内核模块,通常可载入的模块一般是设备驱动程序 |
rmmod | 用于卸载不需要的模块。 |
modinfo | 用于显示内核模块的相关信息。 |
depmod | 用于分析检测内核模块之间的依赖关系。 |
modprobe | 同样用于加载内核模块,与insmod不同,modprobe会根据depmod产生的依赖关系,加载依赖的的其他模块。 |
static int __init func_init(void)
{
}
module_init(func_init);
内核模块程序的基本结构包括了以下几个部分:
Linux的头文件都 存放在/usr/include中。编写内核模块所需要的头文件,并不在上述说到的 目录,而是在Linux内核源码中的 include 文件夹中。
编写内核模块中经常要使用到的头文件有以下两个:<linux/init.h>和<linux/module.h>。我们可 以看到在头文件前面也带有一个文件夹的名字linux,对应了include下的linux文件夹。
在C语言中,static关键字的作用如下。
内核模块的代码,实际上是内核代码的一部分,假如内核模块定义的函数和内核源代码中的某个函数重复了,编译器就会报错,导致编译失败,因此我们给内核模块的代码加上static修饰符的话,那么就可以避免这种错误。
Linux内核的栈资源十分有限,可能只有一个4096字节大小的页,我们编写的函数与Linux内核 共享同一个栈资源。可想而知,如果在我们的模块程序中定义了一个大的局部数组变量,那么有 可能大致导致堆栈溢出,因此,如果需要很大的空间的变量,应该使用动态分配。
#define __init __section(.init.text) __cold notrace
#define __initdata __section(.init.data)
以上代码 __init、__initdata宏定义(位于内核源码/linux/init.h)中的__init用于修 饰函数,__initdata用于修饰变量。
带有 __init 的修饰符,表示将该函数放到可执行文件的 __init 节 区中,该节区的内容只能用于模块的初始化阶段,初始化阶段执行完毕之 后,这部分的内容就会被释放掉。
#define module_init(x) __initcall(x);
宏定义module_init用于通知内核初始化模块的时候,要使用哪个函数进行初 始化。它会将函数地址加入到相应的节区section中,这样的话,开机的时候就可以自动加载模块了。
static void __exit func_exit(void)
{
}
module_exit(func_exit);
使用 __exit,表示将该函数放在可执行文件的 __exit节区,当执行完模块卸载阶 段之后,就会自动释放该区域的空间。
#define __exit __section(.exit.text) __exitused __cold notrace
#define __exitdata __section(.exit.data)
#define module_param(name, type, perm) module_param_named(name, name, type, perm)
#define module_param_array(name, type, nump, perm) module_param_array_named(name, name, type, nump, perm)
在调试内核模块的时候,我们可以使用module_param函数来定义一个变量,控制调试信息的输出。
如果我们定义了一个模块参数,则会在/sys/module/模块名/ parameters下会存在以模块 参数为名的文件。
#define EXPORT_SYMBOL(sym) __EXPORT_SYMBOL(sym, "")
符号指的就是函数和变量。
当模块被装入内核后,它所导出的符号都会记录在内核符号表中。在使用命令insmod加载模块后,模块就被连接到了内核,因此可以访问内核的共用符号。
EXPORT_SYMBOL宏用于向内核导出符号,这样的话,其他模块也可以使用导出的符号了。
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
Linux 是一款免费的操作系统,采用了GPL协议,允许用户可以任意修改其源代码。
GPL协议的主要内容是软件产品中即使使用了某个GPL协议产品提供的库,衍生出一个新产品,该软件产品都必须采用 GPL 协议,即必须是开源和免费使用的,可见GPL协议具有传染性。
在Linux内核版本2.4.10之后,模块必须通过MODULE_LICENSE宏声明此模块的许 可证,否则在加载此模块时,会提示内核被污染。
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
模块信息中“description”信息则来自宏MODULE_DESCRIPTION,该宏用于描述该模块的功能作用。
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
模块信息中“alias”信息来自于宏定义MODULE_ALIAS。该宏定义用于给内核 模块起别名。
注意,在使用该模块的别名时,需要将该模块复制到/lib/modules/内核 源码/下,使用命令depmod更新模块的依赖关系,否则的话,Linux内核不知道这个模块还有另一个名字。
arm 上的案例源码如下。
include <linux/init.h> include <linux/module.h> // 写内核模块必须要包含的; //默认不输出调试信息 //权限有限制 bool debug_on = 0; // 定义了一个布尔类型的模块参数 debug_on,用来打开 debug 消息的输出; module_param(debug_on, bool, S_IRUSR); // 是否开启串口打印信息; static int __init hello_init(void) { if (debug_on) printk("[ DEBUG ] debug info output\n"); printk("Hello World Module Init\n"); // 使用内核提供的打印函数; return 0; } module_init(hello_init); // 使用 hello_init 函数来进行初始化; static void __exit hello_exit(void) { printk("Hello World Module Exit\n"); } module_exit(hello_exit); // 使用宏 module_exit 在内核注册该模块的卸载函数; MODULE_LICENSE("GPL"); // 必须声明该模块使用遵循的许可证,这里我们设置为 GPL 协议。 MODULE_AUTHOR("xiechen"); MODULE_DESCRIPTION("hello world module"); MODULE_ALIAS("test_module");
Linux 上的环境如下。
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ uname -r
5.4.0-42-generic
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.4 LTS
Release: 18.04
Codename: bionic
Linux 上的案例源码如下。
include<linux/module.h> include<linux/kernel.h> include<linux/init.h> static int __init lkp_init(void) { printk("<1>Hello, World! from the kernel space...\n"); return 0; } static void __exit lkp_cleanup(void) { printk("<1>Good Bye, World! leaving kernel space...\n"); } module_init(lkp_init); // 注册模块; module_exit(lkp_cleanup); // 注销模块; MODULE_LICENSE("GPL"); //告诉内核该模块具有GNU公共许可证;
编译Linux内核所采用的Kbuild系统,因此在编译内核模块时,我们需要指定环境变量ARCH和CROSS_COMPILE的值。
KERNEL_DIR=/home/embedfire/module/linux-imx
obj-m := hello_world.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
以上代码中提供了一个关于编译内核模块的Makefile。该Makefile定义了 变量KERNEL_DIR,来保存内核源码的目录。变量obj-m保存着需要编译成模块的目标文件名。 “ ( M A K E ) m o d u l e s “ 实 际 上 是 执 行 L i n u x 顶 层 M a k e f i l e 的 伪 目 标 m o d u l e s 。 通 过 选 项 ” − C ” , 可 以 让 m a k e 工 具 跳 转 到 源 码 目 录 下 读 取 顶 层 M a k e f i l e 。 M = (MAKE) modules“实际上是执行Linux顶层Makefile的伪目标modules。通过选项”-C”,可以让make工具跳 转到源码目录下读取顶层Makefile。M= (MAKE)modules“实际上是执行Linux顶层Makefile的伪目标modules。通过选项”−C”,可以让make工具跳转到源码目录下读取顶层Makefile。M=(CURDIR) 表明然后返回到当前目录,读取并执行当前目录的Makefile,开始编译内核模块。CURDIR是make的 内嵌变量,自动设置为当前目录。
执行“make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-”命令,生成内核模块hello_world.ko。
obj-m := testKernel.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ make make -C /usr/src/linux-headers-5.4.0-42-generic M=/home/xiechen/2.测试中心/2.linux系统编程 modules make[1]: 进入目录“/usr/src/linux-headers-5.4.0-42-generic” CC [M] /home/xiechen/2.测试中心/2.linux系统编程/testKernel.o Building modules, stage 2. MODPOST 1 modules CC [M] /home/xiechen/2.测试中心/2.linux系统编程/testKernel.mod.o LD [M] /home/xiechen/2.测试中心/2.linux系统编程/testKernel.ko make[1]: 离开目录“/usr/src/linux-headers-5.4.0-42-generic” xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ ls Makefile modules.order Module.symvers testKernel.c testKernel.ko testKernel.mod testKernel.mod.c testKernel.mod.o testKernel.o xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ insmod testKernel.ko insmod: ERROR: could not insert module testKernel.ko: Operation not permitted xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ sudo insmod testKernel.ko [sudo] xiechen 的密码: xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ sudo lsmod | grep testKernel testKernel 16384 0 xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ cat syslog | grep Hello cat: syslog: 没有那个文件或目录 xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ cat /var/losyslog | grep Hello local/ lock/ log/ xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ cat /var/log/syslog | grep Hello Aug 6 18:57:11 xiechen-Ubuntu kernel: [ 8143.203249] <1>Hello, World! from the kernel space... xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ rmmod testKernel rmmod: ERROR: ../libkmod/libkmod-module.c:793 kmod_module_remove_module() could not remove 'testKernel': Operation not permitted rmmod: ERROR: could not remove module testKernel: Operation not permitted xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ sudo rmmod testKernel
Linux中,根据设备的类型可以分为三类:字符设备、块设备和网络设备。
字符设备:应用程序按字节/字符来读写数据,通常不支持随机存取。我们常用的键盘、串口都是字符设备。
块设备:应用程序可以随机访问设备数据。典型的块设备有硬盘、SD卡、闪存等,应用程序 可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块的倍数进行。
网络设备是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。
在linux环境直接访问物理内存是很危险的,如果用户不小心修改了内存中的数据,很有可能造成错误甚至系统崩溃。 为了解决这些问题内核便引入了MMU。
MMU为编程提供了方便统一的内存空间抽象,其实我们的程序中所写的变量地址是虚拟内存当中的地址, 倘若处理器想要访问这个地址的时候,MMU便会将此虚拟地址(Virtual Address)翻译成实际的物理地址(Physical Address), 之后处理器才去操作实际的物理地址。
MMU是一个实际的硬件,并不是一个软件程序。他的主要作用是将虚拟地址翻译成真实的物理地址同时管理和保护内存, 不同的进程有各自的虚拟地址空间,某个进程中的程序不能修改另外一个进程所使用的物理地址,以此使得进程之间互不干扰,相互隔离。 而且我们可以使用虚拟地址空间的一段连续的地址去访问物理内存当中零散的大内存缓冲区。
很多实时操作系统都可以运行在无MMU的CPU中, 比如uCOS、FreeRTOS、uCLinux,以前想CPU也运行linux系统必须要该CPU具备MMU,但现在Linux也可以在不带MMU的CPU中运行了。
MMU具有如下功能:
物理地址就是内存单元的绝对地址,好比你电脑上插着一张8G的内存条,则第一个存储单元便是物理地址0x0000, 内存条的第6个存储单元便是0x0005,无论处理器怎样处理,物理地址都是它最终的访问的目标。
当CPU开启了MMU时,CPU发出的地址将被送入到MMU,被送入到MMU的这段地址称为虚拟地址, 之后MMU会根据去访问页表地址寄存器然后去内存中找到页表(假设只有一级页表)的条目,从而翻译出实际的物理地址
include <dt-bindings/input/input.h> include "imx6ull.dtsi" ---------------① / { --------------------② model = "Seeed i.MX6 ULL NPi Board"; compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; aliases { pwm0 = &pwm1; pwm1 = &pwm2; pwm2 = &pwm3; pwm3 = &pwm4; }; chosen { stdout-path = &uart1; }; memory { reg = <0x80000000 0x20000000>; }; reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; linux,cma { compatible = "shared-dma-pool"; reusable; size = <0x14000000>; linux,cma-default; }; }; }; &cpu0 { --------------------③ dc-supply = <®_gpio_dvfs>; clock-frequency = <800000000>; }; &clks { --------------------④ assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>; assigned-clock-rates = <786432000>; }; &fec1 { --------------------⑤ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; status = "okay"; };
sudo apt install gdb -y
gdb -q hello
加-q参数,启动时不会打印gdb的默认信息,界面清爽干净些。
输入list(简写l)命令可以查看程序源代码,默认显示10行,通过回车键不断查看剩余代码。
l
执行run(简写r)命令用于运行代码,在程序结束或者遇到断点处停下。
r
运行break(简写b)命令可以在指定行设置一个断点,断点调试是程序调试里面使用频率最高的技巧。
设置方法为“b”命令后加上相应的行号。
b
info b
c
n
s
continue(简写c):继续执行,到下一个断点处(或运行结束)。
next(简写n):单步执行,跳过子函数。
strp(简写s):单步执行,进入子函数。
使用print(简写p)指令可以查看变量的值,用法为print+变量名。
p i
clear 10
使用clear指令可以删除某一行对应的断点,用法为clear+行号。
finish
使用finish指令可以让程序运行到函数结束位置。
layout src
程序运行以后,使用“layout src”命令可以显示源代码窗口,当前执行代码会高亮标志,单步调试非常方便。
q
执行quit(简写q)会退出gdb调试,返回到控制台终端。
cgbd是gdb的终端界面增强版,相比windows下的visual studio而言, 它的功能显得十分轻量级而没有太多繁杂,它有上下两栏窗口,上栏的窗口支持vi编辑器的语法, 可以方便的使用它来进行字符串定位等功能。在gdb下使用“layout src”时,界面往往容易花屏, 而cgbd更加的稳定可靠。
sudo apt install cgdb -y
核心转储文件(core文件,也被称之为core dump文件,可能某些书籍上称之为“内核转储文件”,都是一样的,不必纠结名称) 是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写入一个磁盘文件, 这个文件就是核心转储文件,它里面包含了进程崩溃时的所有信息,比如包含终止时进程内存的映像、寄存器状态,堆栈指针, 内存管理信息等,而这种信息往往用于调试,比如在gdb调试器使用,导致进程终止运行的信号可以在系统的signal列表中找到。
简单的说,在一个程序崩溃时,系统会在指定目录下生成一个core文件,core文件主要是用来调试的。
在默认情况下,系统是没有打开core文件生成的,可以通过以下命令打开core文件的生成。
ulimit -c unlimited # 改为无限制大小;
系统是否打开了core文件的生成,那么可以通过以下命令判断,如果终端打印的值是0,则代表系统并未开启 生成core文件的功能,而如果是其他数值:比如1024,这代表系统最大可以产生1024字节的core文件,而如果是unlimited则 代表系统不限制core文件的大小,只要有足够的磁盘空间,可以产生10G、100G、甚至是10000G的core文件,当然啦,你的程序 有足够大才行。
ulimit -c # 检验是否打开core文件生成;
ulimit -c 1024000 # 改为限制大小 1024000 字节;
在 profile 文件中添加一句代码 ulimit -c xxxx 即可。
linux系统在多种情况下不会生成核心转储文件:
在默认情况下,系统在进程崩溃时产生的core文件是存在与该进程的程序文件相同的目录下的,并且固定命名为core, 如此此时系统中有多个程序文件都存放在同一个目录下,而恰巧有多个进程崩溃,那么产生的core文件就会相互覆盖, 而如果我们想要分析他们,那就没法去分析了,因此我们可以通过修改配置,让产生的核心转储文件命名包含相应的信息, 而不会导致覆盖,也可以指定核心转储文件的路径。
只需在 /etc/sysctl.conf 文件中,设置kernel.core_pattern的值即可。
vi /etc/sysctl.conf
kernel.core_pattern = core_%e_%p # 这代表着在当前目录下产生core文件;
kernel.core_uses_pid = 0
kernel.core_pattern = /var/core/core_%e_%p # 在/var/core目录下产生core文件;
kernel.core_uses_pid = 0
其中core_pattern的配置中%e, %p分别代表以下参数:
如果 /proc/sys/kernel/core_uses_pid 文件的内容被设置为1,即使 kernel.core_pattern 中没 有设置%p,最后生成的core文件名仍会加上进程ID。
sudo /sbin/sysctl -p # 立刻生效配置;
可以尝试在外部让进程崩溃, 从而产生core文件,根据linux的信号默认的处理行为, SIGQUIT,SIGABRT, SIGFPE和SIGSEGV 都可以让该进程产生core文件, 那么我们可以手动发送这些信号让进程终止并且产生core文件,前提是进程没有处理这些信号。
还有一种方法,在你认为程序可能出现卡死的地方主动调用 abort() 函数产生core文件,这个函数首先取消阻止SIGABRT信号, 然后为调用进程引发该信号(就像调用了 raise() 函数一样),除此之外还有可以使用gdb调试工具来产生core文件。
gdb [程序文件] [core文件] xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ ./targets 这是一个错误 段错误 (核心已转储) xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ ls core_dump.c core_dump.o core_targets_21186 targets test xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ gdb targets core_targets_21186 GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from targets...done. [New LWP 21186] Core was generated by `./targets'. 13 *a = 0x1;
单步调试的案例如下。
下面是gdb调试的步骤: 首先在main函数中打一个断点 (gdb) b main Breakpoint 1 at 0x400b55: file core_dump.c, line 8. 运行到断点处 (gdb) r Starting program: /home/jiejie/embed_linux_tutorial/base_code/linux_debug/core_dump/targets Breakpoint 1, main () at core_dump.c:8 8 int *a = NULL; 单步运行 (gdb) s 10 printf("这是一个错误\n"); (gdb) s 这是一个错误 13 *a = 0x1; 运行到第13行这里就出现错误了 (gdb) s Program received signal SIGSEGV, Segmentation fault. 0x0000000000400b6d in main () at core_dump.c:13 13 *a = 0x1; 退出gdb调试 (gdb) quit A debugging session is active. Inferior 1 [process 19261] will be killed. Quit anyway? (y or n) y
在写代码的时候,我们会封装很多函数,而这些函数之中又会调用其他的函数,当程序每次调用函数的时候,就会跳转到函数的地方去执行,那么这期间就有很多信息产生了。
比如:调用函数的地方,函数的参数,被调用函数的变量等,这些信息其实是存储在栈中的。
其实更确切地说,这些信息是存储在函数调用信息帧中的,每个函数及其变量都被分配了一个帧(frame),这些函数信息帧就组成了函数调用栈。
使用 gdb 调试工具就可以查看函数调用栈的内容信息,可以清晰地看到各个函数的调用顺序以及各函数的输入形参值,是分析程序的执行流程和输入依赖的重要手段。
gdb提供了一些指令可以查看这些帧中的信息,当查询函数变量的信息时,gdb就是从这个被选中的帧内获取信息,但是查看被选中帧外的变量信息是非法的,当程序运行停止的时候,gdb会自动选择当前被调用的函数帧,并且打印简单帧信息。
上面的bt指令主要是查看栈的信息,而每一帧都会有详细的信息,这些函数调用信息帧包括:调用函数的地方,函数的参数等。如果想查看栈中某一帧的信息,首先要做的是切换当前栈。这时候需用用到 frame 指令(缩写形式为 f)。
除了使用 frame 指令切换栈帧外,还可以使用 up 和 down 指令。
info 指令是一个很强大的指令,使用它可以查看各种变量的值,如果我们希望看到详细的函数调用信息帧的信息,如:
可以使用以下指令:
除此之外 info 指令还可以查看当前寄存器的值:
include <sys/types.h> include <unistd.h> include <stdio.h> include <stdlib.h> void test1(int arg) { int num; num = arg; printf("\t\t---我是 test%d 函数\n", num); } void test2(int arg) { int num; num = arg; printf("\t--我是 test%d 函数\n", num); printf("\t-- test%d 开始调用 test1 \n", num); test1(1); printf("\t-- test%d 结束调用 test1 \n", num); printf("\t--结束调用 test%d \n", num); } void test3(int arg) { int num; num = arg; printf("-我是 test%d 函数\n", num); printf("- test%d 开始调用 test2 \n", num); test2(2); printf("- test%d 结束调用 test1 \n", num); printf("-结束调用 test%d \n", num); } int main(void) { test3(3); sleep(1); // 防止进程过快退出 return 0; }
info frame 指令查看函数调用帧的所有信息,在这步操作前我们切换回到test1函数中(切换帧)。
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ gdb a.out GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out...(no debugging symbols found)...done. (gdb) r Starting program: /home/xiechen/2.测试中心/2.linux系统编程/a.out -我是 test3 函数 - test3 开始调用 test2 --我是 test2 函数 -- test2 开始调用 test1 ---我是 test1 函数 -- test2 结束调用 test1 --结束调用 test2 - test3 结束调用 test1 -结束调用 test3 [Inferior 1 (process 22516) exited normally] (gdb) b test1 Breakpoint 1 at 0x55555555468e (gdb) r Starting program: /home/xiechen/2.测试中心/2.linux系统编程/a.out -我是 test3 函数 - test3 开始调用 test2 --我是 test2 函数 -- test2 开始调用 test1 Breakpoint 1, 0x000055555555468e in test1 () (gdb) bt 0 0x000055555555468e in test1 () 1 0x00005555555546fb in test2 () 2 0x0000555555554771 in test3 () 3 0x00005555555547ae in main () (gdb) bt 2 0 0x000055555555468e in test1 () 1 0x00005555555546fb in test2 () (More stack frames follow...) (gdb) bt -2 2 0x0000555555554771 in test3 () 3 0x00005555555547ae in main () (gdb) f 2 2 0x0000555555554771 in test3 () (gdb) f 3 3 0x00005555555547ae in main () (gdb) f 0 0 0x000055555555468e in test1 () (gdb) i f Stack level 0, frame at 0x7fffffffdb80: rip = 0x55555555468e in test1; saved rip = 0x5555555546fb called by frame at 0x7fffffffdbb0 Arglist at 0x7fffffffdb70, args: Locals at 0x7fffffffdb70, Previous frame's sp is 0x7fffffffdb80 Saved registers: rbp at 0x7fffffffdb70, rip at 0x7fffffffdb78
这里面有很多信息:
int fibonacci(int n) { if (n == 1 || n == 2) { return 1; } return fibonacci(n - 1) + fibonacci(n - 2); } int main() { int n = 10; int ret = 0; ret = fibonacci(n); printf("fibonacci(%d)=%d\n", n, ret); return 0; }
调试流程如下。
xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ gcc -g -o test test.c xiechen@xiechen-Ubuntu:~/2.测试中心/2.linux系统编程$ gdb test GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from test...done. (gdb) b f fibonacci frame_dummy (gdb) b fibonacci if n == 5 Breakpoint 1 at 0x656: file test.c, line 5. (gdb) r Starting program: /home/xiechen/2.测试中心/2.linux系统编程/test Breakpoint 1, fibonacci (n=5) at test.c:5 5 if (n == 1 || n == 2) { (gdb) bt 0 fibonacci (n=5) at test.c:5 1 0x0000555555554676 in fibonacci (n=6) at test.c:9 2 0x0000555555554676 in fibonacci (n=7) at test.c:9 3 0x0000555555554676 in fibonacci (n=8) at test.c:9 4 0x0000555555554676 in fibonacci (n=9) at test.c:9 5 0x0000555555554676 in fibonacci (n=10) at test.c:9 6 0x00005555555546ae in main () at test.c:17
一般用来跟踪任何程序的系统调用,比如Linux系统中自带的命令,或者是其他不开源的可执行文件,strace 都可以跟踪他们的系统调用。
strace 命令是一个集诊断、调试、统计与一体的工具,我们用它来监控用户空间进程和内核的交互,比如对应用程序的系统调用、信号传递与进程状态变更等进行跟踪与分析,以达到解决问题或者是了解应用工作过程的目的。
可以清楚地看到这些系统调用的过程、使用的参数、返回值与执行消耗的 时间等,还可以了解它们与操作系统之间的底层交互。
[-e expr]…:指定一个表达式,用来控制如何跟踪。格式如下:
[qualifier=][!]value1[,value2]...
描述说明: qualifier 只能是 trace、abbrev、verbose、raw、signal、read、write 其中之一。
value是用来限定的符号或数字,默认的 qualifier是trace。
感叹号是否定符号,例如: -e open 等价于 -e trace=open ,表示只跟踪 open 调用。
而 -etrace!=open 表示跟踪除了open以外的其他调用,还有两个特殊的符号 all 和 none ,它们分别代表所有选项与没有选项。
strace ls # 跟踪 ls;
strace -c ls # 跟踪并统计 ls 命令;
strace命令在终端输出的信息太多了,我们想要将它重定向输出某个文件中(使用 [-o filename] 参数), 然后对文件进行分析,这样子的操作就比在终端上分析要好得多,但是需要注意的是,因为输出的信息很多, 生成的日志文件可能会很大,所以在日常使用中要注意设置过滤,不需要完全跟踪所有的内容。
strace -o ls.log ls
使用strace命令去跟踪它,看看遇到错误是怎么样的情况。
strace test
使用 -T 参数查看每个系统调用的时间(在输出的最右边 <> 的内容就是时间)。
strace -T test
strace -t -tt test
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。