Lec3-Linux Programming Prerequisite
1. 编程原则
抽象和具体
库(API)的调用与选择:从技术角度,一般使用标准库,如果使用商业库,则会给对方平台带来一定的收益,但是对自己而言,平台移植性比较低。
2. 编程工具
编辑工具:vi, emacs
编译、链接:gcc
调试:gdb
make命令
版本控制工具:CVS等
永久修改环境变量的方法profile
,但是当前窗口修改是不影响的。
3. 编程语言
更高层语言
C/C++,Java,Fortan
ELF binary format
Excutable and Linkable Format
windows下可执行文件的封装格式: MZ
linux下的可执行文件封装格式: ELF,封装一般发生在链接过程中
工具接口标准委员会(TIS)选择了正在发展中的ELF体系上不同操作系统之间可移植的二进制文件格式
脚本
Shell: sh/bash, csh, ksh
Perl, Python tcl/tk, sed, awk
执行方式
编译执行:会先编译为本地的 byte code字节码(C#, VB.net 叫TL),然后再配备对应的解释器(比如java的JVM),解释为CPU可以执行的binary code二进制码,然后放到CPU执行
解释执行:不编译,直接读一行执行一行
4. 开发工具
GCC:Linux下的C编译器,微软的C 编译器:cl,开源编译器:clang,和gcc差不多。
GNU C Compiler -> GNU Compiler Collection
The gcc command: Front end
GDB
GNU Debugger
The gdb command
xxdgb, ddd…
Binary utilities 附带元件
as, ld, ar, ldd…
Make
5. 最简单的编译连接图(Win)
6. 编译链接图(展开)
每一个源代码和目标文件是一一对应的
链接器是将所有的目标文件进行链接
打包后就得到了.a(也就是一个静态库)
为什么需要静态库:
因为随着开发的推进,程序越来越大,则需要通过静态库的方法来降低复杂度。
升级更新要尽量做到是增量更新的
但是静态库会导致复用性降低,磁盘过多被占用
动态库的作用
不放在可执行文件中,放在外面
升级的时候会很方便
动态库会存在冲突(版本问题)
1 2 3 4 5 6 7 8 9 if (){ }else { }#if #else #endif
gcc参数:
-c
:只做编译,不做链接
7. 编译链接图(头文件展开)
头文件
#include<...h>
:预处理阶段,在编译之前 ,按照文件名找到文本文件,将文本内容替换掉这个头文件。
头文件中有什么呢?
1 2 3 4 5 int Qtf (char *) ;int mian (int argc, char ** argv) { Qtf("Hello" ); return 0 }
1 2 3 4 gcc -o lin.o -c linux002.c // 这个不会报错,即编译器不会报错 gcc -o lin.o linux002.c // 这个会报错,链接器会报错
8. 编译链接
头文件和#include (预处理 – 编译时处理)
为什么要做链接?(link)
静态库与动态库
8.1. 依赖库和头文件
静态库(.a 文件):Lab(gcc + ar)
动态库/共享对象(.so 文件):Lab(gcc)
8.2. 其他语言
Java (只有编译+解释执行)
.Net平台 (VB.net , C# C++.net等)
VC++ 及 Delphi 等 (一般称为Win编程)
8.3. 编译的具体过程
8.4. 前端和后端
8.5. 常用命令
gcc -c (编译)
gcc (链接 或者 编译 + 链接)
g++ (C++对应的命令,其实就是换了前端)
8.5.1. gcc可选项(要背,考试会考)
Usage:
gcc [options] [filename]
Basic options:
-E: 只对源程序进行预处理(调用cpp预处理器)
-S: 只对源程序进行预处理、编译
-c: 执行预处理、编译、汇编而不链接
-o output_file: 指定输出文件名
-g: 产生调试工具必需的符号信息
-O/On: 在程序编译、链接过程中进行优化处理
-Wall: 显示所有的警告信息
Basic options:
-Idir
: 指定额外的头文件 搜索路径
-Ldir
: 指定额外的库文件 搜索路径
-lname
: 链接时搜索指定的库文件
-DMACRO[=DEFN]
: 定义MACRO宏(针对#define)
编译后调试一般在一台机器上,而不会在多台机器
补充:
调试的时候仍然使用的是本地编译好的二进制文件
编译的时候没开优化,源代码的语句编译成的汇编码是多条语句,是一对多的关系
调试器:在执行编译后的二进制码,二进制码会被打标签,记录哪一个源代码的哪一行编译而来的。
-g参数:告诉编译器,每一个编译完的二进制码上打上文件名和行号的标签
编译完之后,用其他的机器调试可能是不行的,因为file的路径一般是不一样的。
-O优化,二进制码打乱,不优化的话,源代码和汇编代码是对应着的;一些无关的操作会被编译器扔掉;会把源代码和汇编码之间一对多的关系破坏掉
在不改变源代码的基础上,在文件中添加#define预处理指令。
1 2 gcc -DAA=2# 就相当于在源码中添加了
链接器在链接的时候如何找到库文件?
编译器不需要额外指定头文件的文件名(因为源码中有头文件名)
8.5.2. 文件后缀名
8.5.3. GDB
GDB: GNU Debug
设置断点
监视变量值
单步执行
修改变量值
GDB commands
8.5.4. make & makefile
Multi-file project
IDE
make
make & makefile
makefile描述模块间的依赖关系;
make命令根据makefile对程序进行管理和维护;make判断被维护文件的时序关系
Hello 的 Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 TOPDIR = ../include $(TOPDIR) Rules.mak EXTRA LIBS += : EXEC = $(INSTALL_DIR) /hello OBJS = hello.o all: $(EXEC) # 默认执行make all $(EXEC) : $(OBJS) $(CC) $(LDFLAGS) -0 $@ $(OBJS) $(EXTRA_ LIBS) install: $(EXP_ INSTALL) $(EXEC) $(INSTALL_ DIR) clean: -rm -f $(EXEC) *.elf*.gdb *.o
定义整个工程的编译规则 :
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作 。
自动化编译 :
只需要一个make命令 ,整个工程完全自动编译
make是一个命令工具,是一个解释makefile中指令的命令工具;
默认情况下,每执行一条 makefile 中的命令之前,Shell 终端都会显示出这条命令的具体内容 ,除非该命令用分号分隔而紧跟在依赖关系后面,我们称之为"回显"。如果不想显示命令的具体内容,我们可以在命令的开头加上"@"符号,这种情况通常用于 echo 命令。
${MAKE}
就是预设的 make 这个命令的名称(或者路径)。
GNU make是一个命令工具,是一个用来控制软件构建过程的自动化管理工具。Make工具通过称为Makefile的文件来完成并自动维护编译工作,由Richard Stallman与Roland McGrath设计开发。
Makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接,但是不是所有的文件都需要重新编译,Makefile中记录有文件的信息,在make时会决定在链接的时候需要重新编译哪些文件。
make命令格式:make [-f Makefile] [option] [target]
#make target #make #make clean
8.5.5. make
make [-f filename] [targetname]
Targets
A target is usually the name of a file that is generated by a program; examples of targets are executable or object files.
A target can also be the name of an action to carry out, such as ‘clean’ (phony target).
make install 需要 root 权限
如果 config 的时候使用 root 权限,则编译后产生的所有文件都需要root权限
直接make命令,则执行的就是编译链接的部分 。
1 2 3 4 5 6 7 # automake方式 ./configure #生成新的makefile make make install make uninstall make clean make distclean# 退回到configure之前(删除makefile)
8.5.6. Makefile 规则结构
1 2 target ... : prerequisites ... command
target是一个目标文件,可以是Object File,也可以是执行文件
prerequisites是要生成target所需要的文件或是目标
command是make需要执行的命令。(可以是任意的Shell命令)
举例:依赖关系,如果后面的文件更新,则执行如下代码,若输出文件不存在是执行如下代码则为违反规则。
1 2 3 4 5 6 7 8 hello : main.o kbd.o gcc -o hello main.o kbd.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c clean : rm edit main.o kbd.o
只是匹配次序,并不是执行次序。
make的执行:时间戳检查、文件检查。
8.5.7. Hello的makefile
1 2 3 4 5 6 7 8 9 10 11 12 TOPDIR = ../include $(TOPDIR) Rules.mak EXTRA_LIBS += EXEC = $(INSTALL_DIR) /hello OBJS = hello.oall: $(EXEC) $(EXEC) : $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(EXTRA_LIBS) install: $(EXP_INSTALL) $(EXEC) $(INSTALL_DIR) clean: -rm -f $(EXEC) *.elf *.gdb *.o
8.5.8. Makefile 执行次序
make会在当前目录下找名字叫"Makefile"或"makefile"的文件。
查找文件中的第一个目标文件(target),举例中的hello
如果hello文件不存在,或是hello所依赖的文件修改时间要比hello新,就会执行后面所定义的命令来生成hello文件。
如果hello所依赖的.o文件不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(类似一个堆栈的过程)
make根据.o文件的规则生成 .o 文件,然后再用 .o 文件生成hello文件。
8.5.9. 伪目标
"伪目标"并不是一个文件,只是一个标签,所以make无法生成它的依赖关系和决定它是否要执行,只能通过显示地指明这个"目标"才能让其生效
"伪目标"的取名不能和文件名重名
为了避免和文件重名的这种情况,可以使用一个特殊的标记".PHONY"来显示地指明一个目标是"伪目标",向make说明,不管是否有这个文件,这个目标就是"伪目标"
伪目标一般没有依赖的文件,但也可以为伪目标指定所依赖的文件。
伪目标同样可以作为"默认目标",只要将其放在第一个。
8.5.10. 多目标
用处
当多个目标同时依赖于一个文件,并且其生成的命令大体类似,可以使用一个自动化变量"$@"表示着目前规则中所有的目标的集合
举例
1 2 3 4 5 6 7 8 bigoutput littleoutput : text.g generate text.g -$(subst output,,$@ ) > $@ bigoutput : text.g generate text.g -big > bigoutput littleoutput : text.g generate text.g -little > littleoutput
8.5.11. 预定义变量
$<
第一个依赖文件的名称
$?
所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚
$+
所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$^
所有的依赖文件,以空格分开,不包含重复的依赖文件
$*
不包括扩展名的目标文件名称
$@
目标的完整名称
$%
如果目标是归档成员,则该变量表示目标的归档成员名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o gcc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o OBJECTS = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(OBJECTS) gcc -o edit $(OBJECTS) main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c clean : rm edit $(OBJECTS)
8.5.12. 多目标扩展
语法<targets ...>: <target-pattern>: <prereq-patterns ...> <commands>
例子
目标从$object中获取
“%.o"表明要所有以”.o"结尾的目标,即"foo.o bar.o",就是变量$object集合的模式
依赖模式"%.c"则取模式"%.o"的"%",也就是"foo bar",并为其加下".c"的后缀,于是依赖的目标就是"foo.c bar.c"
1 2 3 4 5 6 7 8 9 10 objects = foo.o bar.oall: $(objects) $(objects) : %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ foo.o : foo.c $(CC) -c $(CFLAGS) foo.c -o foo.o bar.o : bar.c $(CC) -c $(CFLAGS) bar.c -o bar.o
编写方法:
遍历.c文件中的头文件依赖树,把每一个依赖的头文件都放到后面!gcc里面有参数。
不写.h的话:第一次编译连接不会有问题,但是若头文件发生更新,并不会重新编译
多目标扩展
语法:<targets ...>: <target-pattern>: <prereq-patterns ...><commands>...
举例
1 2 3 4 objects = foo.o bar.oall: $(objects) $(objects) : %.o: %. c$(CC) -c $(CFLAGS) $< -o $@
目标从$object中获取
“%.o"表明要所有以”.o"结尾的目标,即"foo.o bar.o",就是变量$object集合的模式
依赖模式"%.c"则取模式"%.o"的"%",也就是"foo bar",并为其加下".c"的后缀,于是依赖的目标就是"foo.c bar.c"
上述规则等价于
1 2 3 foo.o : foo.c$(CC) -c $(CFLAGS) foo.c -o foo.o bar.o : bar.c$(CC) -c $(CFLAGS) bar.c -o bar.o
8.6. 使用函数
调用语法
$(<function> <arguments>)
${<function> <arguments>}
字符串处理函数
$(subst <from>,<to>,<text>)
$(strip <string>)
文件名操作函数
$(dir <names...>)
$(basename <names...>)
foreach 函数
$(foreach <var>,<list>,<text>)
if 函数
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)
call函数
$(call <expression>,<parm1>,<parm2>,<parm3>...)
9. 软件设计原则
清晰原则
吝啬原则
扩展原则
10. File System
文件
可以写入或读取或两者兼有的对象。 文件具有某些属性,包括访问权限和类型。
文件系统
文件及其某些属性的集合。 它为引用这些文件的文件序列号提供了名称空间。
10.1. 文件系统的多种含义
文件系统:Linux内核源代码情景分析,P415
指种特定的文件格式。例如,我们说Linux的文件系统是Ext2, MSDOS的文件系统是FATI6,而Windows NT的文件系统是NTFS或FAT32,就是指这个意思。
指按特定格式进行了"格式化"的块存储介质。当我们说"安装"或"拆卸"一个文件系统时,指的就是这个意思。
指操作系统中(通常在内核中)用来管理文件系统以及对文件进行操作的机制及其实现,这就是本章的主要话题。
10.2. 文件类型和结构
文件类型
常规文件
字符专用文件
特殊块文件
fifo
socket
符号链接
目录
文件结构
字节流; 没有特别的内部结构
10.3. Liunx中的文件系统
10.3.1. VFS模型(考试要考)
虚拟; 仅存在于内存中
组件:
超级块(super block):某一个磁盘的某一个分区的文件系统的信息
记录文件系统类型
记录文件系统的参数
i节点对象(i-node object):index
记录的是真正的文件,文件存储在磁盘上时是按照索引号访问文件的
文件对象(file object)
记录的是文件描述符,索引号
不对应真正的文件,文件open后会创建出文件对象。
文件没有close,则内核中的文件对象就没有释放
目录对象(dentry object)
10.3.2. Ext2 文件系统
10.3.3. 硬链接和符号链接
Hard link
不同的文件名对应同一个inode
不能跨越文件系统
对应系统调用link
Symbolic link
存储被链接文件的文件名(而不是inode)实现链接
可跨越文件系统
对应系统调用symlink
10.3.4. 回顾命令:ls -l
为了显示文件的可访问权限,我们在使用ls命令的时候同时使用-l参数
10.3.5. 系统调用和库函数
都以C函数的形式出现
系统调用
Linux内核的对外接口; 用户程序和内核之间唯一的接口; 提供最小接口
库函数
依赖于系统调用; 提供较复杂功能
例:标准I/O库
10.4. 无缓冲I/O和缓冲I/O
无缓冲I/O
读写 -> 系统调用
文件描述符
并不是ANSI C,而是POSIX.1 和 XPG3
缓冲I/O
通过标准I/O库来实现
处理很多细节,比如缓存分配
流 -> 指向FILE的指针
10.4.1. 标准I/O系统调用
文件描述符
标准I/O
open/creat, close, read, write, lseek
dup/dup2
fcntl
ioctl
10.4.1.1. 文件描述符
文件描述符
一个小的非负整数:int fd
;
在<unistd.h>
中
STDIN_FILENO(0),STDOUT_FILENO(1),STDERR_FILENO(2)
文件操作的一般步骤:open - read/write - [lseek] - close
10.4.1.2. 例子
1 2 3 4 5 6 7 8 9 10 11 12 #include <fcntl.h> main (){ int fd, nread; char buf[1024 ]; fd = open ("data" , O_RDONLY); nread = read (fd, buf, 1024 ); close (fd); }
10.4.1.3. open/creat 函数
打开和建立一个文件或设备
C语言不能够重载,为什么会有2个open?并不是通过重载实现的,两个open是以一个函数的形式提供的,C语言提供了变长参数的函数机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open (const char *pathname, int flags) ;int open (const char *pathname, int flags, mode_t mode) ;int creat (const char *pathname, mode_t mode) ;
10.4.1.4. 参数 flag
“标志”:文件访问方式
O_RDONLY,O_WRONLY或O_RDWR中的一个请求分别按以下方式对文件进行只读,只读或读/写操作,这些操作按零或多个进行按或运算:(全部在/usr/include/fcntl.h中定义 )
O_APPEND:以附加模式打开文件
O_TRUNC:如果文件已经存在并且是常规文件,并且打开方式允许写入,则会将其长度截断为0。
O_CREAT:如果文件不存在,将创建它。
O_EXCL:与O_CREAT一起使用时,如果文件已存在,则为错误,打开失败。
"创建"功能:相当于以等于O_CREAT | O_WRONLY | O_TRUNC的标志打开
使用|
来分割多个参数
10.4.1.5. 参数 mode
mode:指定在创建新文件的情况下使用的权限。
取值情况
需要多个权限的话,也是按位或。S_IRUSR|S_IWUSR
就表示rw-
。
注意这里的权限是4位八进制数 。
10.4.2. mode参数和umask
umask:文件保护机制
新文件的初始访问方式
mode和~umask
10.4.2.1. close 函数
选择一个文件描述符
1 2 3 #include <unistd.h> int close (int fd) ;
注意打开的文件一定要关闭
10.4.2.2. read/write 函数
Read from a file descriptor
1 2 3 #include <unistd.h> ssize_t read (int fd, void *buf, size_t count) ;
Write to a file descriptor
1 2 3 #include <unistd.h> ssize_t write (int fd, const void *buf, size_t count) ;
例子
1 2 3 4 5 while ((n = read (STDIN_FILENO, buf, BUFSIZE)) > 0 ) if (write (STDOUT_FILENO, buf, n) != n) err_sys ("writeerror" ); if (n<0 ) err_sys ("readerror" );
10.4.2.3. lseek函数
重定义读写文件偏移量
1 2 3 4 #include <sys/types.h> #include <unistd.h> off_t lseek (int fildes, off_t offset, int whence) ;
Thedirective"whence":
SEEK_SET:the offset is set to “offset” bytes
SEEK_CUR: the offset is set to its current location plus "offset"bytes
SEEK_END:theoffsetifsettothesizeofthefileplus"offset" bytes
10.4.2.4. dup/dup2函数
Duplicate a file descriptor
1 2 3 4 #include <unistd.h> int dup (int oldfd) ;int dup2 (int oldfd, int newfd) ;
File sharing,Example: redirection
用在重定向中
ls:本质就是对1号文件描述符写数据(无论是printf还是cout)
将原来1号文件描述符从控制台输出转到其他文件中
打开一个文件,就会有一个文件描述符(例如是3号文件描述符),使用dup2系统调用,就可以实现了。
1 2 3 4 dup2(1 , 1000 ); fd=open("aaa.txt" , ...); dup2(fd, 1 ); dup(1000 , 1 );
10.4.2.5. fcntl 函数
Manipulate a file descriptor
1 2 3 4 5 6 #include <unistd.h> #include <fcntl.h> int fcntl (int fd, int cmd) ;int fcntl (int fd, int cmd, long arg) ;int fcntl (int fd, int cmd, struct flock *lock) ;
The operation is determined by"cmd".
The value of"cmd"
F_DUPFD : Duplicate a file descriptor
F_GETFD/F_SETFD :Get/set the filed escriptor’s
close-on exec flag:执行时是否关闭,文件描述符能否从父进程传递到子进程。
F_GETFL/F_SETFL:Get/set the file descriptor’s flags (并不是所有情况都可以setfl的)
F_GETOWN/F_SETOWN: Manage I/O availability signals(告诉当前进程是否I/O传来的信号)(不要求理解深刻)
F_GETLK/F_SETLK/F_SETLKW: Get/set the file lock(暂时不讲)
Example:dup/dup2 and fcntl
fcntl对于文件描述符的操作很全面
pid保存进程id(可以理解为int类型,linux下做了类型的重定义)
if(fd == -1)
:异常处理(文件打开失败)
fcntl(fd, F_SETFD, 1);
这里将close-on-exec flag设置为true,所以调用execl的时候,fd会关闭 。
fork()
:Linux下很特别的系统调用,是用来创建进程的;复制一份父进程,作为父进程的子进程。(注意:被启动的子进程,就从fork()之后继续执行;子进程并不重复执行,某种程度上子进程和父进程是完全一样的;只有fork()系统调用的返回值不一样)
父进程fork()函数的返回值就是子进程的pid,而子进程fork()函数的返回值是0。
子进程是复制了父进程,所以这里子进程有fd这个文件描述符。
exec系列函数的使用
用另外一个程序代替当前进程,不会新开进程 (用当前进程执行新的程序)
启动一段新的程序,将新程序的内存覆盖掉当前进程的内存。
如果没有fork就执行execl,则当前shell的进程没有了(呗新的程序占用了)
注意:
这里是pid==0
,才会执行execl。所以是子进程执行了这个execl 。
execl("ass", "./ass", &fd, NUKK)
:传递的是fd所在地址,这里是int类型的地址,而execl函数要求的传输类型是char类型的地址,所以这里是类型转换。(但是这里的fd的值不能太大,因为是按照char类型读取的,所以fd的值在128以内应该没有问题)
wait(NULL)
:父进程就在这里等待,知道子进程执行完ass
最后的执行结果:test.txt文件中只会有"ooooo",不会有"zzzzz"
10.4.2.6. ioctl Function
Control devices
1 2 #include <sys/ioctl.h> int ioctl (int d, int request, ...) ;
更改一种特殊类型的文件(块类型和字符类型的设备)
10.4.3. 标准I/O库
文件系统
标准I/O函数
10.4.4. 文件流
流和文件结构
FILE * fp;
预定义指针:stdin, stdout, stderr
(封装了012号文件描述符)
缓冲I/O
三类缓冲区
全缓冲区
行缓冲区
无缓冲区
setbuf/setvbuf函数
10.4.5. 流缓冲操作
三种缓冲
块缓冲(完全缓冲)
线缓冲
无缓冲
setbuf,setvbuf函数
1 2 3 #include <stdio.h> void setbuf (FILE *stream,char * buf) ;int setvbuf (FILE *stream,char * buf, int mode, size_t size) ;
补充:
setbuf用于打开或关闭流缓冲机制,参数buf指向一个长度为BUFSIZ(该常量在<stdio.h>
中定义)的缓冲区;如果要关闭缓冲,则将buf设置为NULL即可。
setvbuf用于精确地设置所需的缓冲类型,mode取值如下:_IOFBF(全缓冲)/_IOLBF(行缓冲)/_IONBF(无缓冲);如果指定了mode为带缓冲类型,而buf却为NULL,则系统会自动分配BUFSIZ个字节的缓冲区。
10.4.6. 标准I/O函数
Stream open/close
Stream read/write
每次一个字符的I/O
每次一行的I/O
直接I/O(二进制I/O)
格式化I/O
Stream reposition
Stream flush
10.4.6.1. Stream open/close
Open a stream
1 2 3 #include <stdio.h> FILE *fopen (const char *filename, const char *mode) ;int fclose (FILE *stream) ;
Parameter"mode"
“r”: Open text file for reading .
“w”: Truncate file to zero length or create text file for writing.
“a”: Open for appending (追加).
“r+”: Open for reading and writing .
“w+”: Open for reading and writing. The file is created if it does not exist, otherwise it is truncated .
“a+”: Open for reading and appending . The file is created if does not exist.
Close a stream
1 2 3 #include <stdio.h> int fclose (FILE *fp) ;
10.4.6.2. 输入字符
getc, fgetc, getchar functions
1 2 3 4 5 #include <stdio.h> int getc (FILE *fp) ;int fgetc (FILE *fp) ;int getchar (void ) ;
Three functions:ferror, feof, clearerr
ungetc function: push a character back to a stream.
10.4.6.3. 输出字符
putc, fputc, putchar functions
1 2 3 4 5 #include <stdio.h> int putc (int c, FILE *fp) ;int fputc (int c, FILE *fp) ;int putchar (int c) ;
10.4.6.4. 一行字符串输入
fgets, gets functions
1 2 3 #include <stdio.h> char *fgets (char *s, int size, FILE *stream) ;char *gets (char *s) ;
fgets: reads in at most size-1 characters from stream and stores them into the buffer pointed by s. Reading stops after an EOF or a new line . A “\0” character is stored at the end of the buffer
10.4.6.5. 一行字符串输出
fputs, puts functions
1 2 3 #include <stdio.h> int fputs (const char *s, FILE *stream) ;int puts (const char *s) ;
10.4.7. Question: I/O Efficiency
Rewrite mycat.c
read/write version
getc/putc version
fgetc/fputc version
fgets/fputs version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "ourhdr.h" #define BUFFSIZE 8192 int main (void ) { int n; char buf[BUFFSIZE]; while ((n = read (STDIN_FILENO, buf, BUFFSIZE)) > 0 ){ if (write (STDOUT_FILENO, buf, n) != n){ err_sys ("write error" ); } } if (n < 0 ){ err_sys ("read error" ); } exit (0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "ourhdr.h" int main (void ) { int c; while ((c = getc (stdin)) != EOF){ if (putc (c, stdout) == EOF){ err_sys ("output error" ); } } if (ferror (stdin)){ err_sys ("input error" ); } exit (0 ); }
10.4.7.1. 二进制流输入/输出
fread/fwrite functions
1 2 3 4 #include <stdio.h> size_t fread (void *ptr, size_t size, size_t nmemb, FILE *stream) ;size fwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream) ;
Application:
Read/write a binary array:
1 2 3 float data[10 ];if ( fwrite (&data[2 ], sizeof (float ), 4 , fp) != 4 ) err_sys ("fwriteerror" );
Read/write a structure
1 2 3 4 5 struct { short count; long total; char name[NAMESIZE]; }item;if ( fwrite (&item, sizeof (item), 1 , fp) != 1 ) err_sys ("fwriteerror" );
10.4.7.2. 格式化I/O
scanf, fscanf, sscanf functions
1 2 3 4 #include <stdio.h> int scanf (const char *format, ...) ;int fscanf (FILE *stream, const char *format, ...) ;int sscanf (const char *str, const char *format, ...) ;
使用可变参数,可以解决C语言没有重载的问题。
Use fgets, then parse the string.
printf, fprintf, sprintf functions
1 2 3 4 #include <stdio.h> int printf (const char *format, ...) ;int fprintf (FILE *stream, const char *format, ...) ;int sprintf (char *str, const char *format, ...) ;
10.4.7.3. Reposition a stream
fseek, ftell, rewind functions
1 2 3 4 5 #include <stdio.h> int fseek (FILE *stream, long int offset, int whence) ;long ftell (FILE *stream) ;void rewind (FILE *stream) ;
fgetpos, fsetpos functions ( Introduced in ANSI C)
1 2 3 #include <stdio.h> int fgetpos (FILE *fp, fpos_t *pos) ;int fsetpos (FILE *fp, const fpos_t *pos) ;
10.4.7.4. Flush a stream
刷新文件流。把流里的数据立刻写入文件
注意使用打印定位错误的时候可能由于没有及时刷新无法定位
1 2 #include <stdio.h> int fflush (FILE *stream) ;
10.4.7.5. Stream and File Descriptor
确定流使用的底层文件描述符
1 2 #include <stdio.h> int fileno (FILE *fp) ;
根据已打开的文件描述符创建一个流
1 2 #include <stdio.h> FILE *fdopen (int fildes, const char *mode) ;
10.4.7.6. Temporary File
Create a name for a temporary file
1 2 3 #include <stdio.h> char *tmpnam (char *s) ;
Create a temporary file
1 2 3 #include <stdio.h> FILE *tmpfile (void ) ;
10.4.7.7. Advanced System Calls
Handling file attributes
stat/fstat/lstat, …
Handling directory
10.4.7.8. stat/fstat/lstat functions
Get file status
1 2 3 4 5 6 7 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat (const char *filename, struct stat *buf) ;int fstat (int filedes, struct stat *buf) ;int lstat (const char *file_name, struct stat *buf) ;
10.4.7.9. struct stat
文件属性,实际Linux系统中的结构体字段可能有所不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct stat { mode_t st_mode; ino_t st_ino; dev_t st_rdev; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; off_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; long st_blksize; long st_blocks; };
10.4.7.10. Test macros for file types
10.4.7.11. File Permission - Basics
10.4.7.12. Deep into SUID, SGID, Sticky bit
用来提高权限,SUID将用户提升到root权限,SGID将group提升到root
Authorization in a Linux system is based on file permissions
An SUID or SGID bit on a program elevates your authorization level while running that program to the authorization level of the owner of that program
Typical SUID/SGID programs are su and sudo
10.4.7.13. File permission(第一个0表示八进制)
10.4.7.14. 获取权限
按实际用户ID和实际组ID 测试文件存取权限
1 2 3 #include <unistd.h> int access (const char *pathname, int mode) ;
Parameter"mode":R_OK(可读), W_OK(可写), X_OK(可执行), F_OK(可见)
10.4.7.15. chmod/fchmod functions
Change permissions of a file
1 2 3 4 5 #include <sys/types.h> #include <sys/stat.h> int chmod (const char *path, mode_t mode) ;int fchmod (int fildes, mode_t mode) ;
10.4.7.16. chown/fchown/lchown functions
Change ownership of a file
1 2 3 4 5 6 #include <sys/types.h> #include <unistd.h> int chown (const char *path, uid_t owner, gid_t group) ;int fchown (int fd, uid_t owner, gid_t group) ;int lchown (const char *path, uid_t owner, gid_t group) ;
10.4.7.17. umask function
为进程设置文件存取权限屏蔽字,并返回以前的值
1 2 3 #include <sys/types.h> #include <sys/stat.h> mode_t umask (mode_t mask) ;
10.4.7.18. link/unlink functions
Create a new link to (make a new name for) a file.
1 2 3 #include <unistd.h> int link (const char *oldpath, const char *newpath) ;
Delete a name and possibly the file it refers to.
1 2 3 #include <unistd.h> int unlink (const char *pathname) ;
10.4.7.19. symlink/readlink
Create a symbolic link (named newpath which contains the string “oldpath”)
1 2 3 #include <unistd.h> int symlink (const char *oldpath, const char *newpath) ;
Read value of a symbolic link
1 2 3 #include <unistd.h> int readlink (const char *path, char *buf, size_t bufsiz) ;
10.4.7.20. Handling directories
mkdir/rmdir
chdir/fchdir, getcwd
Read a directory:一些系统调用和命令行命令并不重名
opendir/closedir
readdir
telldir
seekdir
10.4.7.21. mkdir/rmdir functions
创建一个空目录
1 2 3 4 #include <sys/stat.h> #include <sys/types.h> int mkdir (const char *pathname, mode_t mode) ;
删除一个空目录
1 2 3 #include <unistd.h> int rmdir (const char *pathname) ;
10.4.7.22. chdir/fchdir functions
Change working directory
1 2 3 4 #include <unistd.h> int chdir (const char *path) ;int fchdir (int fd) ;
当前工作目录是进程的属性,所以该函数只影响调用 chdir的进程本身:cd(1) command
10.4.7.23. getcwd function
获得当前工作目录的绝对路径
1 2 3 #include <unistd.h> char *getcwd (char *buf, size_t size) ;
10.4.7.24. Read a directory
Data structures
DIR(类型别名), struct dirent
Operations
opendir/closedir
readdir
telldir
seekdir
10.4.7.25. Data structures
DIR
The data type of directory stream objects
in <dirent.h>
typedef struct __dirstream DIR;
struct dirent
Directory item
Defined in <dirent.h>
1 2 ino_t d_ino; char d_name[NAME_MAX + 1 ];
10.4.7.26. Operations
目录的打开、关闭、读、定位
1 2 3 4 5 6 7 #include <sys/types.h> #include <dirent.h> DIR *opendir (const char *name) ;int closedir (DIR *dir) ;struct dirent *readdir (DIR *dir);off_t telldir (DIR *dir) ;void seekdir (DIR *dir, off_t offset) ;
10.4.7.27. 一个目录扫描程序
1 2 3 4 5 6 7 8 9 10 11 12 DIR *dp;struct dirent *entry;if ( (dp = opendir (dir)) == NULL ) err_sys (…);while ( (entry = readdir (dp)) != NULL ) { lstat (entry->d_name, &statbuf); if ( S_ISDIR (statbuf.st_mode) ) … else … }closedir (dp);
10.5. File lock
锁起的作用
几个进程同时操作一个文件
比如多个记事本同时编辑一个文件
锁放在哪里
理论
实践
10.5.1. 文件锁分类
记录锁
劝告锁
检查,加锁有应用程序自己控制
强制锁
检查,加锁由内核控制
影响[open() read() write()]等
共享锁:读锁
排他锁:写锁
10.5.2. 特殊类型
共享模式强制锁:锁上加一些规则,什么规则可以做,什么规则不可以做
租借锁
10.5.3. 标志位
mount -o mand /dev/sdb7 /mnt
super_block
s_flags
MS_MANDLOCK
不是所有文件系统都可以对文件加锁!
10.5.4. fcntl记录锁
用于记录锁的fcntl函数原型
1 2 3 4 #include <unistd.h> #include <fcntl.h> int fcntl (int fd, int cmd, struct flock *lock) ;
10.5.5. struct flock
1 2 3 4 5 6 7 8 9 10 11 struct flock { ... short l_type; short l_whence; off_t l_start; off_t l_len; pid_t l_pid; ... }
10.5.6. cmd参数
cmd参数的取值
F_GETLK:获得文件的封锁信息
F_SETLK:对文件的某个区域封锁或解除封锁
F_SETLKW:功能同F_SETLK, wait方式
10.5.7. 其它封锁命令
lockf函数
1 2 3 #include <sys/file.h> int lockf (int fd, int cmd, off_t len) ;