第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.本书目标
- 强化学生的编程背景知识
- 动态数据结构的应用
- 进程概念和进程管理
- 并发编程
- 定时器和定时功能
- 信号、信号处理和进程间通信
- 文件系统
- 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 的目录结构
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 就会有输出。
(2)创建用户:
现在我们新建一个叫 lilei 的用户:
这个命令不但可以添加用户到系统,同时也会默认为新用户在/home目录下创建一个工作目录:
现在已经创建好一个用户,并且可以使用创建的用户登录,使用如下命令切换登录用户:
(3)用户组:
在 Linux 里面如何知道自己属于哪些用户组呢?
方法一:使用 groups 命令
方法二:查看 /etc/group 文件
在最下面看到 shiyanlou 的用户组信息
也可以使用 grep 命令过滤掉一些不想看到的结果
将其它用户加入 sudo 用户组:
(4)删除用户和用户组:
删除用户:
删除用户组可以使用 groupdel 命令,倘若该群组中仍包括某些用户,则必须先删除这些用户后,才能删除群组。
(5)查看文件权限:
- 显示所有文件大小,并以普通人类能看懂的方式呈现:
(6)变更文件所有者:
切换到 lilei 用户,然后在 /home/lilei 目录新建一个文件,命名为 iphone11。
此时可见文件所有者是lilei。
现在切换回到ycy用户,使用以下命令变更文件所有者为ycy。
(7)修改文件权限:
- 方式一:二进制数字表示
每个文件有三组固定的权限,分别对应拥有者,所属用户组,其他用户,记住这个顺序是固定的。文件的读写执行对应字母 rwx,以二进制表示就是 111,用十进制表示就是 7,对进制转换不熟悉的同学可以看看 进制转换。例如我们刚刚新建的文件 iphone11 的权限是 rw-rw-rw-,换成对应的十进制表示就是 666,这就表示这个文件的拥有者,所属用户组和其他用户具有读写权限,不具有执行权限。
如果我要将文件 iphone11 的权限改为只有我自己可以用那么就可以用这个方法更改它的权限。 - 方式二:加减赋值操作
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 目录:
- 重命名文件
mv命令除了能移动文件外,还能给文件重命名。命令格式为 mv 旧的文件名 新的文件名。
例如将文件“file1”重命名为“myfile”:
- 批量重命名
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 为倒序显示。
可以加上 -n 参数显示行号:
- 使用 more 和 less 命令分页查看文件
打开后默认只显示一屏内容,终端底部显示当前阅读的进度。可以使用 Enter 键向下滚动一行,使用 Space 键向下滚动一屏,按下 h 显示帮助,q 退出。 - 使用 head 和 tail 命令查看文件
只查看文件的头几行(默认为 10 行,不足 10 行则显示全部)和尾几行。
甚至更直接的只看一行, 加上 -n 参数,后面紧跟行数:
(6)查看文件类型:
使用 file 命令查看文件的类型:
(7)编辑文件:
在Linux中编辑文件通常我们会直接使用专门的命令行编辑器比如(emacs,vim,nano),详见第二章
vimtutor
第2章 编程背景
一.知识点归纳
1.Linux中的文本编辑器
(1)vim:
- 普通模式:按a键或者i键进入插入模式。
- 插入模式:可以按ESC键回到普通模式。
- 命令行模式:在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。
- 命令行模式下保存文档:从普通模式输入:进入命令行模式,输入w回车,保存文档。
- 命令行模式下退出vim:从普通模式输入:进入命令行模式,输入wq回车,保存并退出编辑。
(2)gedit
(3)emacs:可输入apt-get install emacs进行安装
- 详细操作见 “ 三.实践内容与截图 ”。
2.C语言数据结构
二.问题与解决思路
1.安装时无法定位软件包
打开系统设置——软件和更新——ubuntu软件,把源选择清华大学的(mirrors.tuna.tsinghua.edu.cn),最后更新一下就可以了。
2.在Linux中,C语言调用汇编代码怎样运行?
3.在Linux中,汇编代码调用C语言怎样运行?
4.编译出现-invalid instruction suffix for push
- 错误原因:在64位系统和32位系统的as命令对于某些汇编指令的处理支持不一样造成的。在.s文件中,包含指令:pushl %ebp,该指令在64位系统下就编译不过。
- 解决方法:在.s文件中,在代码头部添加 .code32 即可。
5.段错误(核心已转储)
- 内存访问出错
这类问题的典型代表就是数组越界。 - 非法内存访问
出现这类问题主要是程序试图访问内核段内存而产生的错误。 - 栈溢出
Linux默认给一个进程分配的栈空间大小为8M。c++申请变量时,new操作申请的变量在堆中,其他变量一般在存储在栈中。 因此如果你数组开的过大变会出现这种问题。
三.实践内容与截图
1. 程序开发(使用教材上代码2.3.1 )
-
程序开发步骤
(1)创建源文件:
使用文本编辑器(gedit或emacs)创建一个或多个程序源文件。在系统编程中,最重要的编程语言是C语言和汇编语言。
(2)用gcc把源文件转换成二进制可执行文件:
(3)gcc是一个程序,它包含三个主要步骤:
- 将C源文件转换为汇编语言文件。 (.c 文件 -> .s 文件)
- 把汇编代码转换成目标代码。 (.s 文件 -> .o 文件)
- 链接。 (所有 .o 文件 -> a.out 文件)
- a.out文件包含以下部分:
- 文件头:文件头包含a.out文件的加载信息和大小,其中
- tsize = 代码段大小
- dsize = 包含初始化全局变量和初始化静态局部变量的数据段的大小
- bsize = 包含未初始化全局变量和未初始化静态局部变量的bss段的大小
- total_size = 加载的 a.out 文件的总大小
- 代码段:也称正文段,其包含程序的可执行代码。代码段从标准C启动代码 crt0.o 开始,该代码调用main()函数。
- 数据段:数据段包含初始化全局变量和初始化静态变量。
- 符号表:可选,仅为运行调试所需。
-
程序执行过程
在类Unix操作系统中,在sh命令行 a.out one two three 执行 a.out 文件。
2. 64位GCC中的运行时堆栈使用情况(使用教材上代码2.4.3 )
(1)t.c文件中包含一个main()函数,调用一个sub()函数:
(2)在64位Linux中,编译 t.c 生成64位汇编的 t.s 文件:
然后,编辑 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"
3. C语言程序与汇编代码的链接
(1)用汇编代码编程(使用教材上代码2.5.1 ):
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 转换为机器码
(2)用汇编语言实现函数(使用教材上代码2.5.2 ):
<1>示例2.3:
<2>示例2.4:
3.Makefile
(1)make程序(使用教材上代码2.7.2 ,书上代码有错误!!!):
(2)makefile示例:
- 创建一个名为mkl的makefile,包括:
- 使用mkl作为makefile运行make:
- 再次运行make命令,会显示: “myt”已是最新
- 相反,如果依赖项列表中的任何文件有更改,make将再次执行rule命令。
(3)makefile中的宏:
- 创建一个叫mk2的makefile,包括:
- 以mk2作为makefile运行make:
- 按前面一样运行生成的二进制可执行文件myt
4.链接库(参考:https://blog.csdn.net/wohu1104/article/details/110789570 )
-
创建4个文件:
(1)静态链接库:
- 创建静态链接库
- 将所有指定的源文件,都编译成相应的目标文件
gcc -c greeting.c name.c
- 然后使用 ar 压缩指令,将生成的目标文件打包成静态链接库,其基本格式如下:
ar rcs 静态链接库名称 目标文件1 目标文件2 ...
重点说明的是,静态链接库的不能随意起名,需遵循如下的命名规则:
libxxx.a
Linux 系统下,静态链接库的后缀名为 .a;
Windows 系统下,静态链接库的后缀名为 .lib;
ar rcs libmyfunction.a greeting.o name.o
- 使用静态链接库
- 首先我们将 main.cpp 文件编译为目标文件:
gcc -c main.c
- 在此基础上,我们可以直接执行如下命令,即可完成链接操作:
gcc -static main.o -L. -lmyfunction
- 由此,就生成了 a.out 可执行文件:
./a.out
(2)动态链接库:
- 创建动态链接库
- 直接使用源文件创建动态链接库,采用 gcc 命令实现的基本格式如下:
gcc -fpic -shared 源文件名... -o 动态链接库名
其中,
- shared 选项用于生成动态链接库;
- fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。
例如,将前面项目中的 greeting.c 、name.c 这 2 个源文件生成一个动态链接库,执行命令为:
gcc -FPIC -shared greeting.c name.c -o libmyfunction.so
- 先使用 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 通常无法直接执行,例如:
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。常用的解决方案有如下几种:
- 将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
- 在终端输入:
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 成功执行。