Linux程序设计(3) Linux Programming
2023-08-09 14:53:19 # NJU # Linux程序设计

Linux Programming Prerequisite

File System

1. What is File and File System?

文件:可以写入或读取或两者兼有的对象。文件具有某些属性,包括访问权限和类型。

文件系统:文件及其某些属性的集合。它为引用这些文件的文件序列号提供了名称空间。

1.1 文件系统的多种含义

  1. 指一种特定的文件格式。例如,我们说Linux的文件系统是Ext2,MSDOS的文件系统是FATI6,而Windows NT的文件系统是NTFS或FAT32,就是指这个意思。
  2. 指按特定格式进行了”格式化”的一块存储介质。当我们说”安装”或”拆卸”一个文件系统时,指的就是这个意思。
  3. 指操作系统中(通常在内核中)用来管理文件系统以及对文件进行操作的机制及其实现,这就是本章的主要话题

1.2 文件类型与结构

文件类型

  1. 普通文件(regular file)
  2. 字符型设备文件(character special file)
  3. 块型设备文件(block special file)
  4. 管道(fifo)
  5. 网络接口(socket)
  6. 符号链接(symbolic link)
  7. 目录(directory):该目录中的文件列表

文件结构:字节流,没有特别的内部结构

1.3 Linux中的文件系统

Virtual File system Switch (VFS)

image-20230317104012704

image-20230317104034363

VFS模型(会考)

虚拟;仅存在于内存中

组件:

  • 超级块(super block):某一个磁盘的某一个分区的文件系统的信息
    • 记录文件系统类型
    • 记录文件系统的参数
  • i-node 对象(i-node object):index
    • 记录的是真正的文件,文件存储在磁盘上时是按照索引号访问文件的
  • 文件对象(file object)
    • 记录的是文件描述符,索引号
    • 不对应真正的文件,文件open后会创建出文件对象。
    • 文件没有close,则内核中的文件对象就没有释放
    • fd 是文件对象数组的下标
  • 目录对象(dentry object)

image-20230317104621277

Ext2 File System(不考)

img

1.4 硬链接和符号链接

Hard link

  1. 不同的文件名对应同一个inode
  2. 不能跨越文件系统
  3. 对应系统调用link

Symbolic link

  1. 存储被链接文件的文件名(而不是inode)实现链接
  2. 可跨越文件系统
  3. 对应系统调用symlink

为了显示文件的可访问权限,我们在使用 ls 命令的时候同时使用 -l 参数

image-20230317105718162

2. 系统调用和库函数

都以C函数的形式出现

系统调用:Linux内核的对外接口; 用户程序和内核之间唯一的接口; 提供最小接口

库函数:依赖于系统调用; 提供较复杂功能

  • 例:标准I/O库

2.1 无缓冲 I/O 和缓冲 I/O

Unbuffered I/O

  • read/write -> System calls
  • File descriptor
  • Not in ANSI C, but in POSIX.1 and XPG3

Buffered I/O

  • 在标准I/O库实现
  • 处理很多细节, 如缓存分配, 以优化长度执行I/O等
  • Stream -> a pointer to FILE

2.2 Basic I/O System Calls

文件描述符

标准I/O

  1. open/creat, close, read, write, lseek
  2. dup/dup2
  3. fcntl
  4. ioctl

文件描述符

File descriptor

  • 一个小的非负整数:int fd;
  • (在<unistd.h>中)
    • STDIN_FILENO(0)
    • STDOUT_FILENO(1)
    • STDERR_FILENO(2)

文件操作的一般步骤:open - read/write - [lseek] - close

1
2
3
4
5
6
7
8
9
10
11
12
/* a rudimentary example program */
#include <fcntl.h>
main(){
int fd, nread;
char buf[1024];
/*open file "data" for reading */
fd = open("data", O_RDONLY);
/* read in the data */
nread = read(fd, buf, 1024);
/* close the file */
close(fd); // 关闭的时候也用文件描述符关闭,释放句柄
}

open/creat 函数

  1. 打开和建立一个文件或设备
  2. C语言不能够重载,为什么会有2个open?并不是通过重载实现的,两个open是以一个函数的形式提供的,C语言提供了变长参数的函数机制(...)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/types.h> // sys:linux系统下提供的头文件
#include <sys/stat.h>
#include <fcntl.h>

// open 也可以创建文件,覆盖了creat的所有功能
// pathname 含路径的文件名
// flags 标志位,是一串二进制数字,可以进行按位或操作,所以是int类型
// mode 文件权限
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// 创建文件的系统调用接口
// flag 默认为只写O_WRONLY和清零O_TRUNC
int creat(const char *pathname, mode_t mode);
// (Return: a new file descriptor if success; -1 if failure)

参数 “flags”

“flags”:文件访问方式

O_RDONLYO_WRONLYO_RDWR中的一个请求分别按以下方式对文件进行只读,只写或读/写操作,这些操作按零或多个进行按位或运算(全部在/usr/include/fcntl.h中定义 )

  • O_APPEND:以附加模式打开文件
  • O_TRUNC:如果文件已经存在并且是常规文件,并且打开方式允许写入,则会将其长度截断为0。
  • O_CREAT:如果文件不存在,将创建它。
  • O_EXCL:与O_CREAT一起使用时,如果文件已存在,则为错误,打开失败。

“creat”函数:相当于以等于O_CREAT | O_WRONLY | O_TRUNC的标志打开

参数 “mode”

mode:指定在创建新文件的情况下使用的权限

需要多个权限的话,也是按位或。S_IRUSR|S_IWUSR就表示rw-

image-20230317113601067

umask:一种文件保护机制

新文件的初始访问方式:mode和~umask

image-20230317113821087

close 函数

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);//可以对文件加锁
//(返回值: 若成功则依赖于cmd,若出错为-1)

The‏ operation‏ is‏ determined ‏by‏”cmd”.

The‏ value‏ of‏”cmd”

  • F_DUPFD:Duplicate a file descriptor
  • F_GETFD/F_SETFD:‏Get/set ‏the‏ file ‏descriptor’s close-on exec flag
    • 执行时是否关闭,文件描述符能否从父进程传递到子进程。
  • F_GETFL/F_SETFL:Get/set ‏the‏ file ‏descriptor’s ‏flags
  • F_GETOWN/F_SETOWN:Manage I/O availability signals
  • F_GETLK/F_SETLK/F_SETLKW:Get/set the file lock

Example:dup/dup2 and fcntl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//file:fcntl
int main()
{
pid_t pid;
fd = open( "test.txt",O_RDWR|O_APPEND);
if (fd == -1)
##printf("open err/n");
printf("fd = %d",fd);
printf("fork!/n");
fcntl(fd,F_SETFD, 1);
char *s="ooooooooooooooooooo";
pid = fork();
if(pid == 0)
execl("ass", "./ass", &fd, NULL);
wait(NULL);
write(fd,s,strlen(s));
close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
//ass 源代码
int main(int argc, char *argv[])
{
int fd;
printf("argc = %d",argc);
fd = *argv[1];
printf("fd = %d",fd);
char *s = "zzzzzzzzzzzzzzzzzzz";
write(fd,(void *)s, strlen(s));
close(fd);
return 0;
}
  1. pid保存进程id(可以理解为int类型,linux下做了类型的重定义)
  2. if(fd == -1):异常处理(文件打开失败)
  3. fcntl(fd, F_SETFD, 1);这里将close-on-exec flag设置为true,所以调用execl的时候,fd会关闭
  4. fork():Linux下很特别的系统调用,是用来创建进程的;复制一份父进程,作为父进程的子进程。(注意:被启动的子进程,就从fork()之后继续执行;子进程并不重复执行,某种程度上子进程和父进程是完全一样的;只有fork()系统调用的返回值不一样)
  5. 父进程fork()函数的返回值就是子进程的pid,而子进程fork()函数的返回值是0。
  6. 子进程是复制了父进程,所以这里子进程有fd这个文件描述符。
  7. exec系列函数的使用
    1. 用另外一个程序代替当前进程,不会新开进程(用当前进程执行新的程序)
    2. 启动一段新的程序,将新程序的内存覆盖掉当前进程的内存。
    3. 如果没有fork就执行execl,则当前shell的进程没有了(呗新的程序占用了)
  8. 注意:
    1. 这里是pid==0,才会执行execl。所以是子进程执行了这个execl
    2. execl("ass", "./ass", &fd, NULL):传递的是fd所在地址,这里是int类型的地址,而execl函数要求的传输类型是char类型的地址,所以这里是类型转换。(但是这里的fd的值不能太大,因为是按照char类型读取的,所以fd的值在128以内应该没有问题)
    3. wait(NULL):父进程就在这里等待,直到子进程执行完ass
    4. 最后的执行结果:test.txt文件中只会有”ooooo”,不会有”zzzzz”

ioctl 函数

控制设备驱动

1
2
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);

2.3 Standard I/O Library

  • File stream
  • Standard I/O functions

File stream

流和”FILE”结构

  • FILE * fp;
  • 预定义指针:stdin, stdout, stderr(封装了012号文件描述符)

缓冲I/O

  • 三类缓冲
    1. 全缓冲
    2. 行缓冲
    3. 无缓冲
  • setbuf/setvbuf 函数

Stream Buffering Operations

Three types of buffering

  • block buffered (fully buffered)‏
  • line buffered
  • unbuffered

setbuf, setvbuf functions

1
2
3
#include <stdio.h>
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int type, size_t size);

type:_IOFBF(满缓冲)_IOLBF(行缓冲) _IONBF(无缓冲)

Standard I/O functions

Stream open/close

Stream read/write

  • 每次一个字符的I/O
  • 每次一行的I/O
  • 直接I/O(二进制I/O)‏
  • 格式化I/O

Stream reposition

Stream flush

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);
// (Return: 0 if success; -1 if failure)

Input of a character

1
2
3
4
5
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
// (Result: Reads the next character from a stream and returns it as an unsigned char cast to an int, or EOF on end of file or error.)

Three functions:ferror, feof, clearerr

ungetc function:push a character back to a stream

Output of a Character

1
2
3
4
5
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
// (Return: the character if success; -1 if failure)

Input of a Line of String

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

fgets:最多从流中读 size-1 字节并存储到 s 指向的缓冲区,读到 EOF或新行结束,“\0” 会存储到缓冲区结尾