0%

Linux程序设计(二)

Linux课程笔记

对应PPT第3章:Working with Files

Files and devices、System calls、Library functions

Linux File Structure

目录是一个文件,其中包含其他文件的索引节点编号和名称。
文件排列在目录中,该目录可能还包含子目录。 这些构成了熟悉的文件系统层次结构。

甚至硬件设备也经常由文件表示。
在UNIX和Linux中都可以找到的三个重要的设备文件是 /dev/console, /dev/tty, 和 /dev/null

/dev/console这个设备代表的是控制台。错误信息和诊断信息通常会被发送到这个设备上。
/dev/tty如果一个进程有控制终端的话,那么特殊文件/dev/tty就是这个终端的的别名。
/dev/null这是空设备。所有写向这个设备的输出都将被丢弃。

System Calls and Device Drivers

我们可以使用少量的功能(称为系统调用)来访问和控制文件和设备。
操作系统的核心(内核)是许多设备驱动程序。 这些是用于控制系统硬件的低级接口的集合。
用于访问设备驱动程序的低级功能(系统调用)包括:

1
2
3
4
5
open:打开文件或设备
read:从打开的文件或设备读取
write:写入文件或设备
close:关闭文件或设备
ioctl:将控制信息传递给设备驱动程序

Library Functions

直接使用低级系统调用进行输入和输出的问题在于它们的效率很低。 为什么?

  1. 进行系统调用会造成性能损失。
  2. 硬件具有局限性,可能会限制低级系统调用可以随时读取或写入的数据块的大小。

为了向设备和磁盘文件提供更高级的接口,Linux发行版提供了许多标准库。

系统调用会影响的性能。系统调用开销大,因为在执行系统调用时,Linux必须从用户代码切换到内核代码运行,然后在返回用户代码。减少这种开销的一个好办法是,在程序中尽量减少系统调用的次数,并且让每次系统调用完成尽可能多的工作。

硬件会对底层系统调用一次所能读写的数据块有一定的限制。例如,磁带机通常的写操作数据块长度是10k,所以如果所写的数据量不是10k的整数倍,磁带机还是会以10k为单位卷绕磁带,这就在磁带上留下了空隙。

Linux系统中各种文件函数与用户、设备驱动程序、内核和硬件之间的关系

Low-Level File Access

每个正在运行的程序(称为进程)都有许多与之关联的文件描述符。
这些是可用于访问打开的文件或设备的小整数。
程序启动时,通常已经打开了其中三个描述符。 这些是:

1
2
3
0:标准输入
1:标准输出
2:标准误差

write

syntax:

1
2
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);

Description:

写系统调用安排将buf中的前n个字节写入与文件描述符fildes关联的文件中。 它返回实际写入的字节数。 如果函数返回0,则表示未写入任何数据。 如果返回-1,则说明写调用中存在错误,该错误将在errno全局变量中指定。

read

syntax:

1
2
#include <unistd.h>
size_t read(int fildes, void *buf, size_t nbytes);

Description:

读取系统调用从与文件描述符fildes关联的文件中读取最多nbytes字节的数据,并将其放置在数据区域buf中。 它返回实际读取的数据字节数,该数目可能少于请求的数目。 如果读取调用返回0,则表示没有任何读取内容。 它到达了文件的末尾。 同样,调用中的错误将导致其返回–1。

open

syntax:

1
2
3
4
5
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const char *path, int oflags);
int open(const char *path, int oflags, mode_t mode);

Description:

open建立到文件或设备的访问路径。 如果成功,则open返回新文件描述符;如果失败,则返回-1,这时open还将设置全局变量errno,以指示失败的原因。

要打开的文件或设备的名称作为参数传递,路径; oflags参数用于指定打开文件时要执行的操作。
将错误指定为强制文件访问模式和其他可选模式的组合。

Mode Description
O_RDONLY Open for read-only
O_WRONLY Open for write-only
O_RDWR Open for reading and writing

该调用还可以在oflags参数中包括以下可选模式的组合(使用按位或):

1
2
3
4
5
O_APPEND: 将写入的数据放在文件末尾。
O_TRUNC: 将文件的长度设置为零,丢弃现有内容。
O_CREAT: 根据需要创建文件,并在模式下指定权限。
O_EXCL: 与O_CREAT一起使用,确保调用者创建文件。
这样可以防止两个程序同时创建文件。 如果文件已经存在,则打开将失败。

使用 O_CREAT 标志打开时创建文件时,必须使用三参数形式。 模式,第三个参数是由头文件 sys /stat.h 中定义的标志的按位”或”构成的。 这些是:

1
2
3
4
5
6
7
8
9
S_IRUSR:读取权限,所有者
S_IWUSR:写入权限,所有者
S_IXUSR:执行权限,所有者
S_IRGRP:读取权限,组
S_IWGRP:写入权限,组
S_IXGRP:执行权限,组
S_IROTH:读取权限,其他
S_IWOTH:写权限,其他
S_IXOTH:执行权限,其他

例如:
open("myfile", O_CREAT, S_IRUSR|S_IXOTH);

umask是一个系统变量,它对创建文件时要使用的文件权限进行编码(设置)。
您可以通过执行umask命令来提供新值来更改变量。
当我们通过open调用创建文件时,会将mode参数与umask进行比较。 在umask中也已设置的mode参数中的任何位设置都将被删除。

close

syntax:

1
2
#include <unistd.h>
int close(int fildes)

Description:

我们使用close终止文件描述符,fildes及其文件之间的关联。 文件描述符可用于重用。 如果成功则返回0,如果错误则返回-1。

lseek

syntax:

1
2
3
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fildes, off_t offset, int whence);

lseek系统调用设置文件描述符fildes的读/写指针;
我们可以将指针设置为文件中的绝对位置或相对于当前位置或文件末尾的位置。

offset参数用于指定位置,whence参数指定如何使用偏移量。 wherece可以是以下之一:

1
2
3
SEEK_SET:偏移量是绝对位置
SEEK_CUR:偏移量相对于当前位置
SEEK_END:偏移量相对于文件末尾

lseek返回偏移量(以字节为单位),偏移量是从文件指针所设置到的文件的开头开始的;如果失败,则返回-1。

fstat, stat, lstat

syntax:

1
2
3
4
5
6
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);

fstat系统调用返回有关与打开的文件描述符关联的文件的状态信息。 信息被写入结构buf,其地址作为参数传递。

相关功能stat和lstat返回命名文件的状态信息。
它们产生相同的结果,除非文件是符号链接。 lstat返回有关链接本身的信息,而stat返回有关链接引用的文件的信息。

stat Member Description
st_mode File permissions and file-type information
st_ino The inode associated the file
st_dev The device the file resides on
st_uid The user identity of the file owner
st_gid The group identity of the file owner
st_atime The time of last access
st_ctime The time of last change to permissions,owner,group,or content
st_mtime The time of last modification to contents
st_nlink The number of hard links to the file

stat结构中返回的st_mode标志还具有在头文件 sys / stat.h 中定义的许多关联宏。
这些宏包括权限和文件类型标志的名称以及一些掩码,以帮助测试特定的类型和权限。
权限标志与前面的开放系统调用相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
文件类型标志包括
S_IFBLK:条目是块特殊设备
S_IFDIR:条目是目录
S_IFCHR:条目是字符特殊设备
S_IFIFO:条目是一个FIFO(命名管道)
S_IFREG:条目是常规文件
S_IFLNK:条目是一个符号链接
其他模式标志包括
S_ISUID:条目在执行时具有setUID
S_ISGID:条目在执行时具有setGID
解释st_mode标志的掩码包括
S_IFMT:文件类型
S_IRWXU:用户读/写/执行权限
S_IRWXG:组读取/写入/执行权限
S_IRWXO:他人的读取/写入/执行权限
定义了一些宏来帮助确定文件类型。
S_ISBLK:测试块特殊文件
S_ISCHR:测试字符专用文件
S_ISDIR:测试目录
S_ISFIFO:测试FIFO
S_ISREG:测试常规文件
S_ISLNK:测试符号链接

dup, dup2

syntax:

1
2
3
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);

dup系统调用提供了一种复制文件描述符的方式,为我们提供了两个或更多访问同一文件的不同描述符。
dup系统调用复制文件描述符fildes,并返回新的描述符。
dup2系统调用通过指定用于复制的描述符有效地将一个文件描述符复制到另一个文件描述符。

The Standard I/O Library

标准I / O库及其头文件 stdio.h 为低级I / O系统调用提供了通用接口。
该库提供了许多用于格式化输出和扫描输入的复杂功能。
它还负责设备的缓冲要求。
低级文件描述符的等效项称为流,并实现为指向结构 FILE * 的指针。
启动程序时,将自动打开三个文件流。 它们是stdinstdoutstderr

fopen

syntax:

1
2
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);

它主要用于文件和终端的输入和输出。
fopen打开由filename参数命名的文件,并将流与其关联。 mode参数指定如何打开文件。 这是以下字符串之一:

1
2
3
4
5
6
"r" or "rb": Open for reading only
"w" or "wb": Open for writing, truncate to zero length
"a" or "ab": Open for writing, append to end of file
"r+" or "rb+" or "r+b": Open for update (reading and writing)
"w+" or "wb+" or "w+b": Open for update, truncate to zero length
"a+" or "ab+" or "a+b": Open for update, append to end of file

如果成功,则fopen返回一个非空的FILE *指针。 如果失败,则返回在stdio.h中定义的值NULL。

fread

syntax:

1
2
#include <stdio.h>
size_t fread(void *ptr, size_t nitems, FILE *stream);

fread库功能用于从文件流读取数据。 从数据流中将数据读入ptr给定的数据缓冲区中。 fread和fwrite都处理数据记录。
这些由记录大小,大小以及要传输的记录数,位数确定。 它返回成功读入数据缓冲区的项目数。
在文件末尾,可能返回的nitem少于零,包括零。

fwrite

syntax:

1
2
#include <stdio.h>
size_t fwrite (const void *ptr, size_t size, size_t nitems, FILE*stream);

它从指定的数据缓冲区中获取数据记录,并将其写入输出流。 它返回成功写入的记录数。

fclose

syntax:

1
2
#include <stdio.h>
int fclose(FILE *stream);

fclose库函数关闭指定的流,从而导致所有未写入的数据被写入。

fflush

syntax:

1
2
#include <stdio.h>
int fflush(FILE *stream);

fflush库函数使文件流上的所有未完成数据立即被写入。

缓冲流

流有3种不同的缓冲类型,分别是:全缓冲、行缓冲、无缓冲。
在LINUX系统中,对新打开的流采用如下默认缓冲类型:

  • 标准错误总是无缓冲的。
  • 其他的流若引用交互设备则是行缓冲,否则是全缓冲的。

术语刷新表示写出缓冲区中的数据。缓冲区可以由标准I/O库函数自动刷新,也可以通过调用fflush函数来刷新。

通常,缓冲区中的数据在下述情况下会自动刷新:

  • 当流被关闭时
  • 当调用exit终止程序时
  • 若流是行缓冲的,当写出一换行符时
  • 当企图输出而缓冲区已满了时
  • 无论何时对流的写入操作导致它实际从文件读数据时

最后一种情形的例子是:当用print输出不带换行符的一个字符串至终端之后,若紧接着调用从终端读数据的函数,则也会导致缓冲区的输出立即被写到终端。

fseek

syntax:

1
2
#include <stdio.h>
int fseek(FILE *stream, long int offset, int whence);

它为该流的下一次读取或写入设置流中的位置。
fseek返回一个整数:如果成功,则返回0;如果失败,则返回-1,并设置errno表示错误。

fgetc, getc, getchar

syntax:

1
2
3
4
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();

fgetc函数从文件流返回下一个字节作为字符。 当到达文件末尾或出现错误时,它将返回EOF。 您必须使用错误或伪造来区分这两种情况。
getc可以实现为宏。
getchar函数等效于getc(stdin),并从标准输入中读取下一个字符。

fputc, putc, putchar

syntax:

1
2
3
4
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

fputc函数将一个字符写入输出文件流。 它返回已写入的值,如果失败则返回EOF。
函数putc等效于fputc,但可以将其实现为宏。
putchar函数等效于putc(c,stdout),将单个字符写入标准输出。

fgets, gets

syntax:

1
2
3
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);

fgets函数从输入文件流中读取字符串。 它将字符写入s指向的字符串中,直到遇到换行符,传输n-1个字符或到达文件末尾为止,以先到者为准。
成功完成后,fgets返回一个指向字符串s的指针。 如果流在文件的末尾,它将为流设置EOF指示器,并且fgets返回空指针。 如果发生读取错误,fgets将返回一个空指针,并设置errno来指示错误的类型。
gets函数类似于fgets,不同之处在于它从标准输入中读取并丢弃遇到的任何换行符。 它将尾随空字节添加到接收字符串。

Formatted Input and Output

如果您使用C进行编程,可能会有许多库函数以受控方式生成输出。
这些功能包括用于将值打印到文件流和scanf的printf和friends,以及用于从文件流读取值的其他函数。

printf, fprintf, sprintf

syntax:

1
2
3
4
#include <stdio.h>
int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

Printf系列函数能够对各种不同类型的参数进行格式化输出。
每个参数在输出流中的表示形式由格式参数format控制的,它是一个包含普通的可打印和称为“转换控制符”的代码的字符串,转换控制符规定了其余的参数应该以何种方式被输出到何种地方。

printf函数在标准输出上生成其输出。
fprintf函数在指定的流上生成其输出。
sprintf函数将其输出和一个终止空字符写入作为参数传递的字符串s中。 该字符串必须足够大以包含所有输出。
printf函数返回一个整数,即写入的字符数。 对于sprintf,这不包括终止null。 错误时,这些函数将返回负值并设置errno。

普通字符在输出时不发生变化。转换控制符让printf取出传递过来的其他参数并对它们的格式进行编排。

1
2
3
4
5
6
7
%d, %i: Print an integer in decimal
%o, %x: Print an integer in octal, hexadecimal
%c: Print a character
%s: Print a string
%f: Print a floating point (single precision) number
%e: Print a double precision number, in fixed format
%g: Print a double in a general format

可以利用字段限定符对数据的输出格式做进一步的控制。它是对转换控制符的扩展,能够对输出数据的间隔进行控制。

一个常见用法是设置浮点数的小数位或设置字符串两端的空格数。

Format Argument Output
%10s “Hello” \ Hello\
%-10s “Hello” \ Hello \
%10d 1234 \ 1234\
%-10d 1234 \ 1234 \
%010d 1234 \ 0000001234\
%10.4f 12.34 \ 12.3400\
%*s 10, “Hello” \ Hello\
%10s “HelloTherePeeps” HelloTherePeeps

scanf, fscanf, sscanf

syntax:

1
2
3
4
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s, const char *format, ...);

非常重要的一点是,用于保存scanf函数扫描的值的变量的类型正确,并且它们与格式字符串精确匹配。
与scanf一样,scanf和friends的格式字符串包含普通字符和转换说明符。
scanf函数返回成功读取的项目数,如果第一个项目失败,则为零。 如果在匹配第一个项目之前到达输入结尾,则返回EOF。 如果文件流发生读取错误,则将设置流错误标志,并将错误变量errno设置为指示错误的类型。

n以下是一些最常用的转换说明符:

1
2
3
4
5
6
7
%d: Scan a decimal integer
%o, %x: Scan an octal, hexadecimal integer
%f, %e, %g: Scan a floating point number
%c: Scan a character (whitespace not skipped)
%s: Scan a string
%[]: Scan a set of characters
%%: Scan a % character

Other Stream Functions

还有许多其他stdio库函数使用流参数或标准流stdin,stdout,stderr:

1
2
3
4
5
6
7
fgetpos:获取文件流中的当前位置。
fsetpos:设置文件流中的当前位置。
ftell:返回流中的当前文件偏移量。
rewind:重置流中的文件位置。
freopen:重用文件流。
setvbuf:设置流的缓冲方案。
remove:等同于取消链接,除非path参数是目录,在这种情况下,它等同于rmdir。

Stream Errors

为了指示错误,许多stdio库函数返回超出范围的值,例如空指针或常量EOF。 在这些情况下,错误在外部变量errno中指示:

1
2
#include <errno.h>
extern int errno;

syntax:

1
2
3
4
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);

ferror函数测试流的错误指示符,如果已设置,则返回非零值,否则返回零。
feof函数测试流中的文件结束指示符,如果已设置,则返回非零,否则返回零。
clearerr函数清除流指向的流的文件结尾和错误指示符,它没有返回值,也没有定义错误。

Streams and File Descriptors

syntax:

1
2
3
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);

我们可以通过调用fileno函数来确定将哪个低级文件描述符用于文件流。 它返回给定流的文件描述符,失败时返回-1。
我们可以通过调用fdopen函数,基于已经打开的文件描述符创建一个新的文件流。
mode参数与fopen函数的参数相同,并且必须与最初打开文件时建立的文件访问模式兼容。 fdopen返回新文件流,如果失败,则返回NULL。

File and Directory Maintenance

chmod and chown

syntax:

1
2
3
4
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);

由path指定的文件已更改为具有mode赋予的权限。 这些模式是在开放系统调用中指定的,是所需权限的按位或。 除非为程序赋予适当的特权,否则只有文件所有者或超级用户可以更改其权限。
该调用使用用户ID和组ID的数值。 如果设置了适当的特权,则文件的所有者和组将被更改。

syntax:

1
2
3
4
#include <unistd.h>
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);

取消链接系统调用将删除文件的目录条目,并减少文件的链接计数。 如果取消链接成功,则返回0;如果失败,则返回-1。 如果计数达到零,并且没有进程打开文件,则删除该文件。
链接系统调用会创建到现有文件path1的新链接。 新目录条目由path2指定。 我们可以使用symlink系统调用以类似的方式创建符号链接。

mkdir and rmdir

syntax:

1
2
3
4
#include <sys/stat.h>
int mkdir(const char *path, mode_t mode);
#include <unistd.h>
int rmdir(const char *path);

mkdir使用路径作为名称创建一个新目录。 目录权限以参数模式传递。
rmdir系统调用除去目录,但前提是它们为空。

chdir and getcwd

syntax:

1
2
3
#include <unistd.h>
int chdir(const char *path);
char *getcwd(char *buf, size_t size);

由于我们在外壳程序中使用cd命令更改目录,因此程序可以使用chdir系统调用。
程序可以通过调用getcwd函数来确定其当前工作目录。
getcwd函数将当前目录的名称写入给定缓冲区buf。 如果目录名称超过缓冲区的大小(以参数大小指定),则返回null。 成功返回buf。

Scanning Directories

目录函数在头文件dirent.h中声明。 他们使用DIR结构作为目录操作的基础。
目录条目本身以dirent结构返回,该结构也在dirent.h中声明,因为永远不要直接更改DIR结构中的字段。

opendir

syntax:

1
2
3
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);

opendir函数打开目录并建立目录流。
如果成功,它将返回指向DIR结构的指针,以用于读取目录条目。 opendir在失败时返回空指针。

readdir

syntax:

1
2
3
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir(DIR *dirp);

readdir函数返回一个指向结构的指针。 连续调用readdir将返回更多目录条目。 发生错误时,在目录末尾,readdir返回NULL。
包含目录条目详细信息的Dirent结构包括以下条目:

  • ino_t d_ino:文件的索引节点
  • char d_name []:文件名

telldir, seekdir, closedir

syntax:

1
2
3
4
5
#include <sys/types.h>
#include <dirent.h>
long int telldir(DIR *dirp);
void seekdir(DIR *dirp, long int loc);
int closedir(DIR *dirp);

telldir函数返回一个值,该值记录目录流中的当前位置。 您可以在随后的对seekdir的调用中使用此命令,以将目录扫描重置为当前位置。
seekdir函数在dirp给定的目录流中设置目录条目指针。 loc的值(用于设置位置)应从先前对telldir的调用中获得。
closedir函数关闭目录流并释放与之关联的资源。 成功返回0,如果有错误则返回1。

Errors

本章中描述的许多系统调用和功能可能由于多种原因而失败。
当这样做时,它们通过设置外部变量errno的值来指示失败的原因。
函数出现问题后,程序必须立即检查errno变量。
错误的值和含义在头文件errno.h中列出。 它们包括

1
2
3
EPERM:不允许操作
ENOENT:没有这样的文件或目录
EINTR:系统调用中断
1
2
3
4
5
6
7
8
EIO: I/O Error
EBUSY: Device or resource busy
EEXIST: File exists
EINVAL: Invalid argument
EMFILE: Too many open files
ENODEV: No such device
EISDIR: Is a directory
ENOTDIR: Isn’t a directory

strerror and perror

syntax:

1
2
3
#include <string.h>
char *strerror(int errnum);
void perror(const char *s);

strerror函数将错误号映射到描述已发生错误类型的字符串中。
perror函数还将errno中报告的当前错误映射为字符串,并将其打印在标准错误流上。 它以字符串s(如果不为null)中给出的消息开头,后跟冒号和空格。

Advanced Topics: mmap

mmap(用于内存映射)功能设置了一段内存,可由两个或更多程序读取或写入。 一个程序所做的更改将被其他程序看到。
从该段读取和写入该段将导致操作系统读取和写入磁盘文件的相应部分。
mmap函数创建一个指向与通过打开的文件描述符访问的文件内容关联的内存区域的指针

syntax:

1
2
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

您可以通过传递off参数来更改共享段访问的文件数据的开始。 打开文件描述符以fildes的形式传递。 可通过len参数设置可访问的数据量。
您可以使用addr参数来请求特定的内存地址。 如果为零,则自动分配结果指针。

prot参数用于设置内存段的访问权限。 这是以下常量值的按位或:

1
2
3
4
PROT_READ:可以读取该段
PROT_WRITE:该段可以写
PROT_EXEC:该段可以执行
PROT_NONE:无法访问细分

flags参数控制程序对段所做的更改如何在其他地方反映。

Argument Description
MAP_PRIVATE The segment is private, changes are local
MAP_SHARED The segment changes are made in the file
MAP_FIXED The segment must be at the given address,addr

msync函数使部分或全部内存段中的更改被写回到映射文件(或从中读取)。

1
2
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);

段的要更新的部分由传递的起始地址addr和长度len给出。 flags参数控制应如何执行更新。

Argument Description
MS_ASYNC Perform asynchronous writes
MS_SYNC Perform synchronous writes
MS_INVALIDATE Read data back in from the file

munmap函数释放内存段。

1
2
#include <sys/mman.h>
int munmap(void *addr, size_t len);