《Unix/Linux 系统编程》第一、二章读书笔记
自学教材1、2章学习笔记
引言部分(第一章)
安装虚拟机并下载Linux
因学院网速较慢,所以我拷贝了同学之前下载好的虚拟机,似乎这个虚拟机也是从老师那里来的。
在Linux环境下运行C语言代码
创建一个名为 test 的C语言文件
按 “i" 编辑该文件
按 ESC 键退出编辑,“shift+:” 输入 ”wq“ 保存并退出
编译 text 文件
输出结果
使用linux
命令执行规则:用户进程通常会执行命令解释程序sh,后者将提示用户执行命令。sh将直接执行一些特殊命令,(cd , exit , logout , &)非特殊命令通常是可执行文件。对于非特殊命令,sh会复刻子进程并等待该子进程终止。子进程会将其执行映像更改为文件,并执行新映像。子进程终止时会唤醒父进程sh,后者将执行另一个命令等。除简单命令,sh还支持 I/O 重定向和通过管道连接的符合命令。除内置命令外,用户还可以开发程序,将其编译为二进制的可执行文件,并按照命令运行程序。
Ubuntu Linux 的特性
(1)Ubuntu 的使用需要输入用户名和密码创建一个默认目录为 ”/home/username“ 的用户账号。当Ubuntu启动时,会自动登录默认用户
Ctrl+Alt+T 打开伪终端
每次开启一个新终端都是运行一个sh,提示用户执行新的命令
(2)出于安全考虑,用户应为普通用户,而不是根用户或者超级用户,要运行任何特权命令,需要输入:
sudo command
//但是会验证用户的密码。
(3)用户的 “PATH” (路径)环境变量设置通常不包括用户的当前目录。当前目录下运行程序,用户需要每次都输入:
./a.out
所以为了方便起见,用户应更改路径设置,已包含当前目录,在用户主目录中,创建一个包括以下代码的 .bashrc 文件:
PATH=$PATH:./
用户每次打开伪终端时,sh 都会先执行.bashrc文件来设置路径,以包含当前工作目录。
(4)Ubuntu 支持有线和无线网络连接
Unix/Linux 文件系统组织
文件系统采用树状组织结构,如下图:
Unix/Linux 将所有能够存储或提供信息的事物都视为文件,从一般意义上来说,文件系统树的每一个节点都是一个 “FILE”(文件)。
文件类型
(1)目录文件:一个目录中可能包含的文件;
(2)非目录文件(只能是叶节点)
REGULAR(常规):普通文本或者可执行的二进制编码;
SPECIAL(特殊):是/dev 目录中的条目。表示 I/O 设备(可分为:字符特殊文件,块特殊文件和其他类型)
(3)符号链接文件:属于常规文件,内容为其他文件的路径名。因此这些文件是指向其他文件的指针。
文件路径名
(1)Unix/Linux 文件系统树的根节点(用 “/" 符号表示)称为根目录(根)。文件系统树的每个节点都由以下表单的默认路径名指定:
/a/b/c/d OR a/b/c/d
以 “ / " 开头的路径名为绝对路径名,反之相对于进程当前的工作目录(CWD)的相对路径名。
用户登录到Unix/Linux时,CWD即被设为用户的主目录。
CWD可以通过cd(更改目录)命令修改。
pwd命令可以打印CWD的绝对路径名。
Unix/Linux 命令
可查与课本 P16
Linux 手册页
Linux 将在线手册页保存在标注 /usr/man/ 目录下。在Ubuntu Linux下,手册页保存在/usr/share/man 目录下。手册页分为不同类型,用man1、man2等表示。
所有手册页均为压缩 .gz 文件。包括描述性文本,说明了使用参数和选项的命令。
系统管理
用户账户
与在Linux中一样,用户账户信息保存在/etc/passwd 文件中,该文件归超级用户所有,但任何人都可以读取。在表单的/etc/password 文件中,每个用户都有一个对应的记录行。
用户登录时,Linux将检查/etc/password文件和/etc/shadow文件,以验证身份。登陆成功后,登录进程将通过获取用户的gid和uid来转换成用户进程,并将目录更改为用户的homeDir,然后执行列出的 initialProgram,该程序通常为命令解释程序sh。
添加新用户
sudo adduer username
sudo 命令
sudo——超级用户执行。允许用户以超级用户的身份执行命令,执行完命令,用户进程恢复到原来的特权级别。
第二章(编程背景)
Linux中的文本编辑器
vim
h: 将光标向左移动一个字符;
l: 将光标向右移动一个字符;
j:将光标向下移动一行;
K:将光标向上移动一行。
i:切换到插入模式,插入文本;
a:切换到插入模式,追加文本。
//疑问:i和a的区别?
退出插入模式:
按 “ESC” -> ":" ->
w:写入(保存)文件;
q:退出vim;
wq:保存并退出;
q!:不保存更改,强制退出。
emacs的集成开发
安装 emacs 到虚拟机
程序开发步骤
创建源文件:用文本编辑器创建一个或多个程序源文件。
全局变量在函数外定义。
局部变量在函数内定义。
仅对定义它们的函数可见,默认情况下,局部变量是自动变量,他们在函数调用时出现,按逻辑在函数退出时消失。
对于寄存器变量,编译器试图把他们分配到CPU寄存器里。由于自动局部变量在函数调用前没有分配内存空间,因此编译时不能初始化。
全局变量具有唯一性,并且有一个副本。
初始化的全局变量在编译时赋值;未初始化的全局变量在程序开始执行时清零。
静态全局变量仅对定义他们的文件可见。
具有永久性和唯一性,可以初始化。
非静态全局变量则对同一程序的所有文件都可见。
易失形变量,C语言支持·,这些变量用作内存映射 I/O 的地址,或者通过中断处理程序或多个执行线程来访问的全局变量
gcc可以把源文件转换成二进制可执行文件
第一步:将C源文件转换成汇编文件。第一步是调用C编译器,将 .c 文件转换成包含目标机器汇编代码的 .s 文件。
第二步:将汇编代码转换成目的代码。汇编器是将汇编代码转换成二进制形式机器代码的程序。生成 .o 文件。简单来说,第二步就是将 .s 文件转换为 .o 文件。
第三步:链接。一个程序可能包含多个 .o 文件。链接器将所有的 .o 文件和必要的库函数组合成单一的二进制可执行的文件。
静态与动态链接
静态链接,因为所有必要的库函数代码和数据都要纳入a.out文件中。使得a.out文件完整独立,但通常很大。
动态链接,操作系统会把a.out文件和共享库均加载到内存中,使加载的库代码在执行期间可以供a.out 文件访问。动态链接的优点是:
1.可减小每个a.out文件的大小;
2.多个执行程序可以共享相同的库函数;
3.修改库函数不需要重新编译源文件。
动态链接所用的库称为动态链接库(DLL)。在Linux中称为共享库( .so 文件)。
程序执行过程
(1)读取 a.out 文件,确定所需的总内存大小,包括堆栈空间大小;
(2)sh 从总大小中分配一个内存区给执行映像。sh将 bss 段清零,使得所有未初始化全局变量和未初始化静态局部变量以初始值0开始,执行期间,堆栈向下朝低位地址延伸。
(3)然后,sh放弃旧映像,开始执行新映像;
(4)执行从 crt0.o开始,调用main(),将 argc 和 argv 作为参数传递给 main(),可写成:
int main( int argc , char *argv[]) {.........}
其中 argc 为命令行参数的数量,每个 argv[] 指向对应的命令行参数字符串。
程序终止
(1)正常终止:程序执行成功,main()最终回到 ctr0.o,调用库函数 exit(0)终止进程;
(2)异常终止:遇到错误(例如无效地址、非法指令、越权等),会陷入操作系统内核,内核的陷入处理程序将错误类型转换为一个信号,使进程终止。
64位GCC中的运行时堆栈的使用情况
64位模式下的函数调用惯例
(1)下面的 t.c 文件包含C中的一个main()函数,它定义了9个局部变量 int(32位)变量,从a到i。它调用了一个有8个 int 参数的 sub()函数。
(2)在64位的Linux中,编译 t.c 生成64位汇编的 t.s:
可生成如下汇编文件:
链接库
链接库中包含预设编译的目标代码。在链接期间,链接器使用链接库完成链接过程。在 Linux 中,有两种链接库:用于静态链接的静态链接库和用于动态链接的动态链接库。
静态链接库:
动态链接库
MakeFile
make是一个程序,他按顺序读取 makefile 和 Makefile,以自动有选择地执行编译链接。
Make程序
//我认为make程序的实践过程还是有诸多可写。
make 程序读取 makefile 时,他通过比较依赖项列表中的源文件的时间戳来确定要构建哪些目标项。
依赖项在上次构建后有较新时间戳,make 将执行与目标项有关的规则。
三个C语言程序:
本处书上所给代码有两处问题:
(1)type.h代码中,应在 int mysum(int x , int y)后加一个“ ; ”;
(2)mysum.h 中第二行,应为 “include”。
之后将三个文件写好,执行:
gcc -o myt t.c mysum.c
生成一个名为myt的二进制可执行文件。
Makefile 事例
(1)创建一个名为mk1的makefile;
myt: type.h t.c mysum.c
gcc -o myt t.c mysum.c
(2)使用mk1 作为运行make:make通常使用默认的Makefile或者makefile,即当前目录中出现的makefile。
可以用 -f 标志直接使用另一个makefile。
make -f mk1
在这过程中遇到了一些问题:
然后参照以下链接得以解决:https://blog.csdn.net/sinat_39150454/article/details/73466542
GDB调试工具
GDB断点设置指令总结:
(gdb)break 7 //以行号设置断点
(gdb)break function_name //以函数名设置断点
(gdb)clear 行号 //删除这行的断点
(gdb)clear 函数名 //删除该函数的断点
(gdb)delete breakpoints n //删除第n次(指定编号)设置的断点
(gdb)clear //删除程序中所有的基于行设置的断点
(gdb)delete //删除程序中所有的断点
(gdb)r //执行程序
(gdb)n //单步调试
(gdb)c //执行到下一个断点
(gdb)print 变量或表达式 //打印变量或表达式当前的值。
(gdb)print 变量=值 //对变量进行赋值
(gdb)whatis 变量或表达式 //显示变量类型
(gdb)set variable 变量=值 //变量赋值
C语言结构体
(1)结构体是包括变量或数据对象集合的复合数据类型。C语言结构体类型由 struct 关键字定义。
next:指向下一个节点结构体的指针;
key:一个整数;
name:一个由64个字符组成的数组。
(2)定义结构体时,该结构体的每个字段都必须具有一个·1编译器已知的类型,但自引用指针除外;
每个C语言结构体数据对象都分配了一个连续内存块。C语言结构体的单个字段通过使用 .operater (.运算符)访问。
x.next:指向另一个NODE类型对象的指针;
x.key:这是一个整数;
x.name:这是64个字符组成的数组。
运行时,每个字段相对于结构体起始地址的偏移量进行访问。
(3)一个结构体的大小可以根据sizeof(struct type)确定。C编译器将计算该结构体的总字节数大小。由于内存排列受限制,C编译器可能会用额外字节填充结构体的某些字段。如果需要,用户可以用PACKED属性定义C语言结构体。
(4)假设“NODE x , y"为两个相同类型的结构体,除了复制粘贴,还可以通过C语句y=x 将 x 分配给 y ;
(5)C语言联合体与结构体类似。要定义一个联合体,只需要将关键字 struct 替换成关键字union,如:
编程项目:Unix/Linux文件系统树模拟器
文件树通常是上下颠倒的,根节点在上面。
为了安全起见,假设文件系统只包括目录(DIR)和常规文件(FILE),即没有特殊文件,这些文件是I/O设备。
在Linux文件系统中,每个节点都由表单 /a/b/c 或 a/b/c的唯一路径名表示。以“/"开头表示绝对路径,说明从根开始,否则为相对于当前工作目录(CWD)的相对路径。
命令规范
mkdir 路径名:为给定的路径名创建一个新目录
rmdir 路径名:如果目录为空,则删除该目录
creat 路径名:创建一个FILE结点
rm路径名:删除一个FILE节点
save 文件名:将当前文件系统树保存为文件
reload 文件名:从一个文件构造一个文件系统树
menu:显示有效命令菜单
quit:保存文件系统树,然后终止程序