《Unix/Linux系统编程》学习笔记1

第1章 引言

一.知识点归纳

1.Unix的历史

Unix是一种通用操作系统。该系统诞生于20世纪70年代早期,由肯·汤普森和丹尼斯·里奇采用贝尔实验室的PDP-11微型计算机开发。1975年,贝尔实验室向公众发布了Unix,称为V6 Unix。

2.Linux的历史

Linux是一个类Unix系统。它最初是林纳斯·托瓦斯在1991年为基于INtel x86的个人计算机开发的一个实验室内核。后来,世界各地的人都开始加入Linux的研发队伍。在20世纪90年代末,Linux与GNU相结合,纳入了许多GNU软件,极大地促进了Linux的进一步发展。不久之后,Linux实现了访问互联网的TCP/IP协议族,并移植了支持GUI的X11,成为一个完整的操作系统。

3.本书目标

  1. 强化学生的编程背景知识
  2. 动态数据结构的应用
  3. 进程概念和进程管理
  4. 并发编程
  5. 定时器和定时功能
  6. 信号、信号处理和进程间通信
  7. 文件系统
  8. TCP/IP和网络编程

4.Unix/Linux命令

  • ls:ls dirname:列出CWD或目录的内容。
  • cd dirname:更改目录。
  • pwd:打印CWD的绝对路径名。
  • touch filename:更改文件名时间戳(如果文件不存在,则创建文件)。
  • cat filename:显示文件内容。
  • cp src dest:复制文件。
  • mv src dest:移动或重命名文件。
  • mkdir dirname:创建目录。
  • rmdir dirname:移除(空)目录。
  • rm filename:移除或删除文件。
  • ln oldfile newfile:在文件之间创建链接。
  • find:搜索文件。
  • grep:搜索文件中包含模式的行。
  • ssh:登录到远程主机。
  • gzip filename:将文件压缩为.gz文件。
  • gunzip file.gz:解压.gz文件。
  • tar -zcvf file.tgz .:从当前目录创建tar文件。
  • tar -zxvf file.tgz .:从.tgz文件中解压文件。
  • man:显示在线手册页。
  • zip file.zip filenames:将文件压缩为.zip文件。
  • unzip file.zip:解压.zip文件。

5.一些常用快捷键

  • Ctrl+c:强行终止当前程序
  • Ctrl+d:键盘输入结束或退出终端
  • Ctrl+s:暂停当前程序,暂停后按下任意键恢复运行
  • Ctrl+z:将当前程序放到后台运行,恢复到前台为命令fg
  • Ctrl+a:将光标移至输入行头,相当于Home键
  • Ctrl+e:将光标移至输入行末,相当于End键
  • Ctrl+k:删除从光标所在位置到行末
  • Alt+Backspace:向前删除一个单词
  • Shift+PgUp:将终端显示向上滚动
  • Shift+PgDn:将终端显示向下滚动

6.Linux 的目录结构

image

7.课上用过的Linux命令

二.问题与解决思路

1.网络无法连接

  • 还原默认设置
  • 使用nmcli,在命令端输入:

sudo nmcli networking off
sudo nmcli networking on

再重启网络:

sudo service network-manager restart

三.实践内容与截图

1.用户及文件权限管理

(1)查看用户:

who am i
或者
who mom likes

有一点需要注意的是,在某些环境中 who am i 和 who mom likes 命令不会输出任何内容,这是因为当前使用的 Shell 不是登录式 Shell(login shell),没有用户与 who 的 stdin 相关联,因此不会输出任何内容。登录 Shell 是指用户使用自己的 user ID 登录交互式 shell 的第一个进程,判断是不是登录 Shell 可以执行 echo $0 命令,如果返回 zsh、/bin/zsh、/bin/bash 这种格式,说明是非登录式 Shell(non-login shell);如果返回 -zsh、-bash 则说明是登录式 Shell,这时你执行 who am i 就会有输出。
image

(2)创建用户:

现在我们新建一个叫 lilei 的用户:
image
这个命令不但可以添加用户到系统,同时也会默认为新用户在/home目录下创建一个工作目录:
image
现在已经创建好一个用户,并且可以使用创建的用户登录,使用如下命令切换登录用户:
image

(3)用户组:

在 Linux 里面如何知道自己属于哪些用户组呢?
方法一:使用 groups 命令
image
方法二:查看 /etc/group 文件
在最下面看到 shiyanlou 的用户组信息
image
也可以使用 grep 命令过滤掉一些不想看到的结果
image
将其它用户加入 sudo 用户组:
image

(4)删除用户和用户组:

删除用户:
image
删除用户组可以使用 groupdel 命令,倘若该群组中仍包括某些用户,则必须先删除这些用户后,才能删除群组。

(5)查看文件权限:

image
image
image

  • 显示所有文件大小,并以普通人类能看懂的方式呈现:
    image

(6)变更文件所有者:

切换到 lilei 用户,然后在 /home/lilei 目录新建一个文件,命名为 iphone11。
image
此时可见文件所有者是lilei。
现在切换回到ycy用户,使用以下命令变更文件所有者为ycy。
image

(7)修改文件权限:

  • 方式一:二进制数字表示
    image
    每个文件有三组固定的权限,分别对应拥有者,所属用户组,其他用户,记住这个顺序是固定的。文件的读写执行对应字母 rwx,以二进制表示就是 111,用十进制表示就是 7,对进制转换不熟悉的同学可以看看 进制转换。例如我们刚刚新建的文件 iphone11 的权限是 rw-rw-rw-,换成对应的十进制表示就是 666,这就表示这个文件的拥有者,所属用户组和其他用户具有读写权限,不具有执行权限。
    如果我要将文件 iphone11 的权限改为只有我自己可以用那么就可以用这个方法更改它的权限。
  • 方式二:加减赋值操作
    image
    g、o还有u分别表示group(用户组)、others(其他用户)和user(用户),+和-分别表示增加和去掉相应的权限。

2.Linux 目录结构及文件基本操作

(1)新建:

  • 新建空白文件
    创建名为test的空白文件,因为在其它目录没有权限,所以需要先cd~切换回用户的Home目录:

cd ~
touch test

  • 新建目录
    创建名为“mydir”的空目录:

mkdir mydir

使用 -p 参数,同时创建父目录(如果不存在该父目录),如下我们同时创建一个多级目录(这在安装软件、配置安装路径时非常有用):

mkdir -p father/son/grandson

(2)复制:

  • 复制文件
    将之前创建的 test 文件复制到 /home/shiyanlou/father/son/grandson 目录中:

cp test father/son/grandson

  • 复制目录
    要成功复制目录需要加上 -r 或者 -R 参数,表示递归复制:

cd /home/ycy
mkdir family
cp -r father family

(3)删除:

  • 删除文件
    使用rm命令删除一个文件:

rm test

有时候你会遇到想要删除一些为只读权限的文件,直接使用 rm 删除会显示一个提示,你如果想忽略这提示,直接删除文件,可以使用 -f 参数强制删除:

rm -f test

  • 删除目录
    跟复制目录一样,要删除一个目录,也需要加上 -r 或 -R 参数:

rm -r family

遇到权限不足删除不了的目录也可以和删除文件一样加上 -f 参数:

rm -rf family

(4)移动文件与文件重命名:

  • 移动文件
    使用mv命令移动文件(剪切)。命令格式是 mv 源目录文件 目的目录
    例如将文件“file1”移动到 Documents 目录:
    image
  • 重命名文件
    mv命令除了能移动文件外,还能给文件重命名。命令格式为 mv 旧的文件名 新的文件名
    例如将文件“file1”重命名为“myfile”:
    image
  • 批量重命名

cd /home/shiyanlou/
使用通配符批量创建 5 个文件:
touch file{1..5}.txt
批量将这 5 个后缀为 .txt 的文本文件重命名为以 .c 为后缀的文件:
rename 's/.txt/.c/' *.txt
批量将这 5 个文件,文件名和后缀改为大写:
rename 'y/a-z/A-Z/' *.c

(5)查看文件:

  • 使用 cat,tac 和 nl 命令查看文件
    前两个命令都是用来打印文件内容到标准输出(终端),其中 cat 为正序显示,tac 为倒序显示。
    image
    可以加上 -n 参数显示行号:
    image
  • 使用 more 和 less 命令分页查看文件
    image
    打开后默认只显示一屏内容,终端底部显示当前阅读的进度。可以使用 Enter 键向下滚动一行,使用 Space 键向下滚动一屏,按下 h 显示帮助,q 退出。
  • 使用 head 和 tail 命令查看文件
    只查看文件的头几行(默认为 10 行,不足 10 行则显示全部)和尾几行。
    image
    甚至更直接的只看一行, 加上 -n 参数,后面紧跟行数:
    image

(6)查看文件类型:

使用 file 命令查看文件的类型:
image

(7)编辑文件:

在Linux中编辑文件通常我们会直接使用专门的命令行编辑器比如(emacs,vim,nano),详见第二章

vimtutor


第2章 编程背景

一.知识点归纳

1.Linux中的文本编辑器

(1)vim:

  • 普通模式:按a键或者i键进入插入模式。
  • 插入模式:可以按ESC键回到普通模式。
  • 命令行模式:在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。
  1. 命令行模式下保存文档:从普通模式输入:进入命令行模式,输入w回车,保存文档。
  2. 命令行模式下退出vim:从普通模式输入:进入命令行模式,输入wq回车,保存并退出编辑。
    image

(2)gedit

(3)emacs:可输入apt-get install emacs进行安装

image

  • 详细操作见 “ 三.实践内容与截图 ”。

2.C语言数据结构

二.问题与解决思路

1.安装时无法定位软件包

image
打开系统设置——软件和更新——ubuntu软件,把源选择清华大学的(mirrors.tuna.tsinghua.edu.cn),最后更新一下就可以了。

2.在Linux中,C语言调用汇编代码怎样运行?

3.在Linux中,汇编代码调用C语言怎样运行?

4.编译出现-invalid instruction suffix for push

image

  • 错误原因:在64位系统和32位系统的as命令对于某些汇编指令的处理支持不一样造成的。在.s文件中,包含指令:pushl %ebp,该指令在64位系统下就编译不过。
  • 解决方法:在.s文件中,在代码头部添加 .code32 即可。
    image

5.段错误(核心已转储)

image

  1. 内存访问出错
    这类问题的典型代表就是数组越界。
  2. 非法内存访问
    出现这类问题主要是程序试图访问内核段内存而产生的错误。
  3. 栈溢出
    Linux默认给一个进程分配的栈空间大小为8M。c++申请变量时,new操作申请的变量在堆中,其他变量一般在存储在栈中。 因此如果你数组开的过大变会出现这种问题。

三.实践内容与截图

1. 程序开发(使用教材上代码2.3.1

  • 程序开发步骤

(1)创建源文件:

使用文本编辑器(gedit或emacs)创建一个或多个程序源文件。在系统编程中,最重要的编程语言是C语言和汇编语言。
image

(2)用gcc把源文件转换成二进制可执行文件:

image

(3)gcc是一个程序,它包含三个主要步骤:

  1. 将C源文件转换为汇编语言文件。 (.c 文件 -> .s 文件)
  2. 把汇编代码转换成目标代码。 (.s 文件 -> .o 文件)
  3. 链接。 (所有 .o 文件 -> a.out 文件)
  • a.out文件包含以下部分:
  1. 文件头:文件头包含a.out文件的加载信息和大小,其中
    • tsize = 代码段大小
    • dsize = 包含初始化全局变量和初始化静态局部变量的数据段的大小
    • bsize = 包含未初始化全局变量和未初始化静态局部变量的bss段的大小
    • total_size = 加载的 a.out 文件的总大小
  2. 代码段:也称正文段,其包含程序的可执行代码。代码段从标准C启动代码 crt0.o 开始,该代码调用main()函数。
  3. 数据段:数据段包含初始化全局变量和初始化静态变量。
  4. 符号表:可选,仅为运行调试所需。
  • 程序执行过程

在类Unix操作系统中,在sh命令行 a.out one two three 执行 a.out 文件。
image

2. 64位GCC中的运行时堆栈使用情况(使用教材上代码2.4.3

(1)t.c文件中包含一个main()函数,调用一个sub()函数:

image

(2)在64位Linux中,编译 t.c 生成64位汇编的 t.s 文件:

image
然后,编辑 t.s 文件,删除编译器生成的非必要行,并添加注释来解释代码操作,如下:

点击查看代码
#====== t.s file generated by 64-bit GCC compiler ======
	.globl	sub
sub:		   # int sub(int a,b,c,d,e,f,g,h)

# first 6 parameters a,b,c,d,e,f are in registers
#			rdi,rsl,rdx,rcx,r8d,r9d
# 2 extra parameters g,h are on stack.
# Upon entry, stack top contains g,h
#   ------------------------------------
#   ... ...| h | g | PC |    LOW address
#   -----------------|------------------
#			rsp
# establish stack frame	
	pushq	%rbp
	movq	%rsp, %rbp
# no need to shift rsp down because each function has a 128 bytes
# reserved stack area.
# rsp will be shifted down if function define more locals

# save first 6 parameters in registers on stack
	movl	%edi, -20(%rbp)	# a
	movl	%esi, -24(%rbp)	# b
	movl	%edx, -28(%rbp)	# c
	movl	%ecx, -32(%rbp)	# d
	movl	%r8d, -36(%rbp)	# e
	movl	%r9d, -40(%rbp)	# f
	
# access locals u,v,w at rbp -4 to -12
	movl	$9, -12(%rbp)
	movl	$10, -8(%rbp)
	movl	$11, -4(%rbp)
	
# compute x+g+u+v:
	movl	-20(%rbp), %edx
	movl	16(%rbp), %eax
	addl	%eax, %edx
	movl	-12(%rbp), %eax
	addl	%eax, %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	
# did not shift  rsp down , so just pspQ to restore rbp
	popq	%rbp
	ret
	
#====== main function code in assembly ======
	.globl	main
	.type	main, @function
main:
# establish stack frame	
	pushq	%rbp
	movq	%rsp, %rbp
	
# shift rsp down 48 bytes for locals
	subq	$48, %rsp
	
# locals are at rbp -4 to -32
	movl	$1, -36(%rbp)	# a = 1
	movl	$2, -32(%rbp)	# b = 2
	movl	$3, -28(%rbp)	# c = 3
	movl	$4, -24(%rbp)	# d = 4
	movl	$5, -20(%rbp)	# e = 5
	movl	$6, -16(%rbp)	# f = 6
	movl	$7, -12(%rbp)	# g = 7
	movl	$8, -8(%rbp)	# h = 8
	
# call sub(a,b,c,d,e,f,g,h): first 6 parameters in registers
	movl	-16(%rbp), %r9d	# f in r9
	movl	-20(%rbp), %r8d # e in r8
	movl	-24(%rbp), %ecx # d in ecx
	movl	-28(%rbp), %edx # c in edx
	movl	-32(%rbp), %esi # b in esi
	movl	-36(%rbp), %eax # a in eax but will be in edi
	
# push 2 extra parameters h,g on stack
	movl	-8(%rbp), %edi	# int h in edi
	pushq	%rdi		#pushQ rdi ; only low 32-bits = h
	movl	-12(%rbp), %edi	# int g in edi
	pushq	%rdi		#pushQ rdi ; low 32-bits = g
	
	movl	%eax, %edi	# parameter a in edi
	call	sub		# call sub(a,b,c,d,e,f,g,h)
	
	addq	$16, %rsp	# pop stack: h,g, 16 bytes
	movl	%eax, -4(%rbp)	# i=sub return value in eax
	
	movl	$0, %eax	#return 0 to crt0.o
	leave
	ret

# GCC compiler version 11.2.0
	.ident	"GCC: (Ubuntu 11.2.0-19ubuntu1) 11.2.0"

image
image

3. C语言程序与汇编代码的链接

(1)用汇编代码编程(使用教材上代码2.5.1 ):

image

cc -m32 -S a.c ==> a.s file
gcc -E name.c -o name.i 预编译过程,从.c到.i;
gcc -S name.i -o name.s 编译,将预处理的C代码转化为汇编码;
gcc -C name.s -o name.o 转换为机器码

image

image

(2)用汇编语言实现函数(使用教材上代码2.5.2 ):

<1>示例2.3:

image
image

<2>示例2.4:

image

3.Makefile

(1)make程序(使用教材上代码2.7.2 ,书上代码有错误!!!):

image

image

image

(2)makefile示例:

  1. 创建一个名为mkl的makefile,包括:
    image
  2. 使用mkl作为makefile运行make:
    image
  3. 再次运行make命令,会显示: “myt”已是最新
  4. 相反,如果依赖项列表中的任何文件有更改,make将再次执行rule命令。

(3)makefile中的宏:

  1. 创建一个叫mk2的makefile,包括:
    image
  2. 以mk2作为makefile运行make:
    image
  3. 按前面一样运行生成的二进制可执行文件myt

4.链接库(参考:https://blog.csdn.net/wohu1104/article/details/110789570

  • 创建4个文件:

image

image

(1)静态链接库:

  • 创建静态链接库
  1. 将所有指定的源文件,都编译成相应的目标文件

gcc -c greeting.c name.c

  1. 然后使用 ar 压缩指令,将生成的目标文件打包成静态链接库,其基本格式如下:

ar rcs 静态链接库名称 目标文件1 目标文件2 ...

重点说明的是,静态链接库的不能随意起名,需遵循如下的命名规则:

libxxx.a

Linux 系统下,静态链接库的后缀名为 .a
Windows 系统下,静态链接库的后缀名为 .lib

ar rcs libmyfunction.a greeting.o name.o

  • 使用静态链接库
  1. 首先我们将 main.cpp 文件编译为目标文件:

gcc -c main.c

  1. 在此基础上,我们可以直接执行如下命令,即可完成链接操作:

gcc -static main.o -L. -lmyfunction

  1. 由此,就生成了 a.out 可执行文件:

./a.out

image

(2)动态链接库:

  • 创建动态链接库
  1. 直接使用源文件创建动态链接库,采用 gcc 命令实现的基本格式如下:

gcc -fpic -shared 源文件名... -o 动态链接库名

其中,

  • shared 选项用于生成动态链接库;
  • fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。

例如,将前面项目中的 greeting.c 、name.c 这 2 个源文件生成一个动态链接库,执行命令为:

gcc -FPIC -shared greeting.c name.c -o libmyfunction.so

  1. 先使用 gcc -c 指令将指定源文件编译为目标文件,再由目标文件生成动态链接库

gcc -c -FPIC greeting.c name.c

在此基础上,接下来利用上一步生成的目标文件,生成动态链接库:

gcc -shared greeting.o name.o -o libmyfunction.so

  • 使用动态链接库
    执行如下指令,即可借助动态链接库成功生成可执行文件:

gcc main.c libmyfunction.so -o main

注意,生成的可执行文件 main 通常无法直接执行,例如:
image
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。常用的解决方案有如下几种:

  1. 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
  2. 在终端输入:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx

其中 xxx 为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效);
3. 修改 ~/.bashrc 或 ~/.bash_profile 文件,即在文件最后一行添加:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx

其中 xxx 为动态链接库文件的绝对存储路径,保存之后,执行 source bashrc 指令(此方式仅对当前登陆用户有效)。

在本示例中采用第二种方案,即在终端输入

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ycy

即可使 main 成功执行。
image

posted @ 2022-09-04 09:10  油菜园12号  阅读(123)  评论(0编辑  收藏  举报