Linux系统编程第七八章学习笔记
前言
此文章意在于介绍多种文件系统,操作系统中的不同操作级别,包括为文件存储准备存储设备、内核中的文件系统支持函数、系统调用、文件流上的I/O库函数、用户命令和各种操作的sh脚本。系统性概述了从用户空间的文件流读/写到内核空间的系统调用直到底层的设备I/O驱动程序级别,包括低级别的文件操作,如磁盘分区、显示分区表的示例程序、文件系统的格式化分区以及挂载磁盘分区。介绍了Linux系统的EXT2文件系统,包括系统数据结构、超级块、组描述符、块和索引节点位图以及目录内容的示例程序。此外,还讲解了如何使用系统调用进行文件操作,系统调用的作用和Linux的在线手册页,文件操作中最常用的系统调用,硬链接和符号链接文件,stat系统调用,文件描述符和读写系统调用的使用。
第七章
1.文件操作级别
文件操作分为五个级别,按照从低到高的顺序排列如下。
(1)硬件级别:硬件级别的文件操作包括:
- fdisk:将硬盘、U盘或SDC盘分区。
- mkfs:格式化磁盘分区,为系统做好准备。
- fsck:检查和维修系统。
- 碎片整理:压缩文件系统中的文件。
其中大多数是针对系统的实用程序。普通用户可能永远都不需要它们,但是它们是创建和维护系统不可缺少的工具。
(2)操作系统内核中的文件系统函数:每个操作系统内核均可为基本文件操作提供支
持。下文列出了类Unix系统内核中的一些函数,其中前缀k表示内核函数。
kmount (),kumount ()//(mount /umount file systems )
kmkdir(),krmdir ()//(make/remove directory)
kchdir(), kgetcwd()//(change directory, get CWD pathname)
klink(), kunlink()//(hard link/unlink files)
kchmod(), kchown(), kutime() //(change r |w|x permissions; owner, time)
kcreat (),kopen()//(create/open file for R, W, RW, APPEND)
kread(), kwrite ()//(read/write opened files )
klseek(); kclose()//(1seek/c1ose file descriptors )
ksymlink(),kreadlink( )//(create/read symbolic link files)
kstat(),kfstat(), klstat() //(get file status /information)
kopendir(),kreaddir( )//(open/read airectories)
(3)系统调用:用户模式程序使用系统调用来访问内核函数。例如,下面的程序可读取文件的第二个1024字节。
#include <fcnt1 .h>
int main(int argc,char *argv[ ]}// run as a.out fil ename
(
int fd, n;
char buf [1024] ;
if ((fd = open (argv[1],O_RDONLY)) < 0) // if open() fails
exit(1) ;
1seek(fd,1024, SEEK_ SET) ;// Iseek to byte 1024
n = read(fd,buf, 1024);// try .to read 1024 bytes
close(fd) ;
open()、read()、 lseek() 和close()函数都是C语言库函数。每个库函数都会发出一个系统调用,使进程进入人内核模式来执行相应的内核函数,例如open可进入kopen(),read可进人kread()函数,等等。当进程结束执行内核函数时,会返回到用户模式,并得到所需的结果。在用户模式和内核模式之间切换需要大量的操作(和时间)。因此,内核和用户空间之间的数据传输成本昂贵。虽然可以发出read(fd, buf, 1)系统调用来只读取一个字节的数据,但是这种做法是不明智的,因为一个字节也会带来可怕的高成本。我们在每次必须进入内核时,都要尽可能不虚此行。对于读1写文件,最好的方法是匹配内核的功能。内核会按数据块大小(从1 KB到8KB)来读取/写入文件。例如,在Linux中,硬盘的默认数据块大小是4KB,软盘的是1KB。因此,每个读/写系统调用还要尝试一次传输一个数据块。
(4) I/0 库函数:系统调用可让用户读/写多个数据块,这些数据块只是一系列字节。它们不知道,也不关心数据的意义。用户通常需要读/写单独的字符、行或数据结构记录等。如果只有系统调用,用户模式程序则必须自己从缓冲区执行这些操作。大多数用户会认为这非常不方便。为此,C语言库提供了一系列标准的I/O函数,同时也提高了运行效率。I/O库函数包括:
FILE mode I/O: fopen(), fread(); Ewrite() ,fseek(), fclose(), fflush()
char mode I/O: getc(), getchar() ugetc(); putc() ,putchar()
1ine mode I/O: gets(), fgets(); puts(), fputs()
formatted I/0: scanf(), fscanf(),sscanf(); printf(), fprintf (), sprintf()
(5)用户命令:用户可以使用Unix/Linux命令来执行文件操作,而不是编写程序。用户命令的示例如下:
mlcdir, rmdir, cd,pwd, 18,1ink, un1ink, 工m,cat, Cp, mv, chmod, etc
(6)sh脚本:虽然比系统调用方便得多,但是必须要手动输人命令,如果使用的是GUI,必须要拖放文件图标和点击指向设备来输人,操作烦琐而且耗时。sh脚本是用sh编程语言编写的程序,可通过命令解释程序sh来执行。sh语言包含所有的有效Unix/Linux命令。它还支持变量和控制语句,如if、do、 for、while、 case等。实际上,sh 脚本广泛用于Unix/Linux系统编程。除sh之外,Perl 和Tel等其他许多脚本语言也使用广泛。
2.文件I/O操作
建议看该文章以学习详情Linux系统编程——系统调用之 I/O 操作(文件操作)
3.低级别文件操作
1.分区
一个块存储设备, 如硬盘、U盘、SD卡等,可以分为几个逻辑单元, 称为分区。各分区均可以格式化为特定的文件系统,也可以安装在不同的操作系统上。大多数引导程序,如GRUB、LILO等,都可以配置为从不同的分区引导不同的操作系统。分区表位于第一个扇区的字节偏移446 ( OxIBE)处,该扇区称为设备的主引导记录(MBR)。表有4个条目,每个条目由一个16字节的分区结构体定义,即:
stuct partition {
u8 drive;// 0x80 - active
u8 head;// starting head
u8 sector;// starting sector
u8 cylinder:// starting. cylinder.
u8 sys_ type;// partition type
u8 end_ head;// end head
u8 end_ sector ;// end. sector
u8 end_ _cylinder; // end cylinder
u32 start_ sector; // startingsector counting from 0
u32 nr_ sectors ; // number of sectors in partition
};
实践指令如下:
dd if=/dev/zero of=disk20191314 bs=1024 count=1440//创建一个名为mydisk的虛拟磁盘映像文件。
fdisk mydisk//运行fdisk
详细指令
使用help
得到
2.格式化分区&&3.挂载分区
第八章
1.系统调用
在操作系统中,进程以两种不同的模式运行,即内核模式和用户模式,简称Kmode和Umode。在Umode中,进程的权限非常有限。它不能执行任何需要特殊权限的操作。特殊权限的操作必须在Kmode下执行。系统调用(简称sysall)是一-种允许进程进人Kmode以执行Umode不允许操作的机制。复刻子进程.修改执行映像,甚至是终止等操作都必须在内核中执行。
2.系统调用手册页
在Unix以及大多数版本的Linux中,在线手册页保存在/usr/man/目录中(Goldt等1995 ; Kerrisk 2010, 2017 )。而在Ubuntu Linux中,则保存在/usr/share/man目录中。man2子目录中列出了所有系统调用手册页。sh 命令man 2 NAME显示了系统调用名称的手册页。
例如:
man 2 stat: display man pages of stat(), fstat() and 1stat() syscalls
man 2 open: display man pages of open() syscal1
man 2 read: display man pages of read() sysca11, etc.
许多系统调用需要特别包含头文件,手册页的SYNOPSIS (概要)部分列出了这些文件。如果没有合适的头文件,C编译器可能会因为syscall函数名称类型不匹配而发出许多警告。一些系统调用可能还需要特定的数据结构作为参数,必须在手册页中描述这些参数。
3.使用系统调用进行文件操作
系统调用必须由程序发出。它们的用法就像普通函数调用- -样。每个系统调用都是一个库函数,它汇集系统调用参数,并最终向操作系统内核发出一个系统调用。
简单指令如下:
access:检查对某个文件的权限
int access (char *pathname, int mode) ;
chdir:更改目录
int chdir(const char *path);
chmod:更改某个文件的权限
int chmod(char *path, mode_ .t mode);
chown:更改文件所有人
int chown(char *nane, int uid, int g14); ,
chroot:将(逻辑)根目录更改为路径名
int chroot (char pathnome);
getewd:获取CWD的绝对路径名
char *getewd(char *buf, int size);
mkdir:创建目录
int mkdir (char tpathname, mode. t mode) ;
rmdir:移除目录(必须为空)
int mdir (ohar *pathname); ,
link:将新文件名硬链接到旧文件名
int link(char *o14path, char *newpath);
unlink:减少文件的链接数;如果链接数达到0,则删除文件
int unlink (char *pathnane) ;
symlink:为文件创建一个符号链接
int syplink(char *o1dpath, char *nerpath);
rename:更改文件名称
int rename(char *o1dpath, char *newpath);
utime:更改文件的访问和修改时间
int utime(char *pathname, struct utimebuf time)
以下系统调用需要超级用户权限。
mount:将文件系统添加到挂载点目录上
int mount (char specialfile, char *mountDir);
umount:分离挂载的文件系统
int- umount(char *dir)p
mknod:创建特殊文件
int mkned(char tpath, int mode, int device);
4.常用的系统调用
如下:
stat:获取文件状态信息
int stat (char * filename, struct stat *buf )
int fstat (int filedes, struct stat *buf)
int lstat (char *filename, struct stat *buf)
open:打开一个文件进行读、写、追加
int open(char *file, int flags, int mode )
close:关闭打开的文件描述符
int close(int fd)
read:读取打开的文件描述符
int read(int fd, char buf[ ],int count )
write:写入打开的文件描述符
int write(int fa, char buf[ ],int count)
Iseek:重新定位文件描述符的读/写偏移量
int lseek(int fd, int offset, int whence)
dup:将文件描述符复制到可用的最小描述符编号中
int dup(int oldfd) ;
dup2:将oldfd复制到newfd中,如果newfd已打开,先将其关闭:
int dup2 (int olafd, int newfa)
link:将新文件硬链接到旧文件
int link (char *o1dPath, char *newPath)
unlink:取消某个文件的链接;如果文件链接数为0,则删除文件
int unlink (char *pathname) ;
symlink:创建一个符号链接
int symlink (char *target, char *newpath)
readlink:读取符号链接文件的内容
int readlink(char *path, char *buf, int bufsize)
umask:设置文件创建掩码;文件权限为(mask & ~umask)
int umask(int umask) ;
5.链接文件
在Unix/Linux中,每个文件都有-一个路径名。但是,Unix/Linux 允许使用不同的路径名来表示同一个文件。这些文件叫作LINK (链接)文件。有两种类型的链接,即硬链接和.软链接或符号链接。
1.硬链接文件&&2.符号链接文件
详情看大佬的Linux 文件系统 & 软/硬链接文件
6.stat系统调用
输入man 2 stat
可获得关于stat的帮助,详情请观看man page
- opendir&&readdir
使用man 3 opendir
或man 3 readdir
得到
7.open-close-lseek系统调用
open:打开一个文件进行读、写、追加
int open(char *file, int flags, int mode) ;
close:关闭打开的文件描述符
int close (int fd) ;
read:读取打开的文件描述符
int read(int fd, char buf[ ],int count) ;
write:写人打开的文件描述符
int write(int fd, char buf[ ],int count);
Iseek:将文件描述符的字节偏移量重新定位为偏移量
int 1seek(int fa, int offset,int whence) ;
umask:设置文件创建掩码;文件权限为(mask & ~umask)
8.read()系统调用
read)将n个字节从打开的文件描述符读入用户空间中的buf[]。返回值是实际读取的字节数,如果read()失败,会返回-1,例如当fd无效时。注意,buf] 区必须有足够的空间来接收n个字节,并且返回值可能小于n个字节,例如文件小于n个字节,或者文件无更多需要读取的数据。还要注意,返回值是一个整数,而不是文件结束(EOF)符,因为文件中没有文件结束符。文件结束符是I/O库函数在文件流无更多数据时返回的一个特殊整数值(-1)。
9.write()系统调用
write()将n个字节从用户空间中的buf[ ]写人文件描述符,必须打开该文件描述符进行写、读写或追加。返回值是实际写入的字节数,通常等于n个字节,如果write()失败,则为-1,例如由于出现无效的fd或打开fd用于只读等。
10.文件操作示例程序
系统调用适用于大数据块上的文件I/O操作,即不需要行、字符或结构化记录等的操作。
事例如下:在linux环境下实现文件的读写操作
苏格拉底挑战
文件操作知识问题
当然,让我们开始以苏格拉底的方式思考文件操作知识点:
- 什么是文件路径,以及在文件操作中,为什么文件路径如此重要?
非常好的解释!接下来的问题:
- 什么是相对路径和绝对路径,它们之间有什么区别?在什么情况下你会选择使用相对路径,而在什么情况下使用绝对路径更合适?
相对路径的区别:
相对于当前工作目录,只需提供文件相对于当前目录的路径信息。
对于不同的工作目录,相对路径可能会引用不同的文件。
绝对路径的区别:
从文件系统的根目录开始,提供完整的路径信息。
在不同的工作目录下,绝对路径总是引用同一个文件。
在选择使用相对路径或绝对路径时,需要根据具体情况进行判断:
在需要指定固定位置的文件或目录时,使用绝对路径更合适。
当文件或目录的位置相对于当前工作目录,并且可能在不同的工作目录下使用时,使用相对路径更直观和方便。
非常清晰的解释!下一个问题:
- 在文件操作中,什么是文件读取(file reading)和文件写入(file writing)?它们之间有什么主要的区别和用途?
文件写入是指将数据从内存写入到磁盘或其他存储设备中的文件中。文件写入适用于需要长期保存数据或保存更改的应用程序,比如文本编辑器、数据库、日志文件记录器等。在文件写入期间,应用程序会打开文件、写入文件内容,然后关闭文件,以免对其他应用程序的访问产生影响。
文件读取和文件写入之间的主要区别在于它们的方向。文件读取是磁盘/存储设备到内存的方向,用于加载文件内容,而文件写入则是内存到磁盘/存储设备的方向,用于保存或更新文件内容。
非常好的描述!接下来的问题:
- 在文件操作中,什么是文件打开和文件关闭?为什么在进行文件读取和文件写入时需要明确进行这两个步骤?
文件打开是指将磁盘或其他存储设备中的文件加载到内存中,并创建一个用于访问文件的“文件句柄”。文件句柄是一个唯一的标志符,用于标识打开的文件,并提供对文件的读写操作。在打开文件时,需要指定所要访问的文件名,以及该文件的打开模式(只读、只写、读写等)。
文件关闭是指将内存中的文件数据保存到磁盘或其他存储设备中,释放文件句柄占用的系统资源。在关闭文件时,需要将所有对内存中文件的修改保存到磁盘中,防止数据丢失或文件损坏。
文件打开和关闭是必须的,因为它们确保了操作系统与打开文件的应用程序之间的正确协作。打开文件时,应用程序需要从操作系统中请求并分配系统资源来管理文件。操作系统要求关闭文件是为了释放这些资源,使它们可用于其他应用程序。
非常正确!接下来的问题:
- 在文件操作中,什么是文件模式(file mode)?可以举例说明几种常见的文件模式,以及它们分别适用于什么类型的文件操作?
文件模式(file mode)是用于指定打开文件时所采用的文件访问模式或权限。文件模式定义了对文件的操作方式,例如读取、写入、追加、执行等,以及文件的可见性和权限控制。
以下是几种常见的文件模式以及它们的用途:
只读模式('r'):这种模式用于打开文件以进行读取操作。文件以只读方式打开,不允许对文件进行写入操作。适用于需要读取文件内容但不需要修改文件的情况。
只写模式('w'):这种模式用于打开文件以进行写入操作。如果文件已经存在,将会清空文件内容;如果文件不存在,将会创建新文件。适用于需要覆盖或创建新文件并写入数据的情况。
追加模式('a'):这种模式用于打开文件以进行写入操作,但不会清空文件内容。新的数据将被添加到文件的末尾。适用于需要在已有文件的末尾添加数据的情况。
读写模式('r+'):这种模式用于打开文件以进行读取和写入操作。文件以允许读取和写入的方式打开。适用于需要同时读取和修改文件的情况。
二进制模式('b'):可以与上述模式结合使用,例如,'rb'表示以二进制只读模式打开文件。二进制模式用于处理二进制文件,如图像、音频或视频文件。
文本模式('t'):也可以与上述模式结合使用,例如,'wt'表示以文本只写模式打开文件。文本模式用于处理文本文件,以便更容易处理文本编码。
这些文件模式可以根据特定的文件操作需求来选择,确保对文件的操作方式与预期一致,并提供了对文件权限的控制。
您对文件操作级别的知识点的理解非常不错!您已经清楚地解释了文件路径、相对路径和绝对路径的区别,以及文件读取、文件写入、文件打开和文件关闭的概念。此外,您还了解了文件模式的作用和常见的文件模式示例。
如果您还有任何关于文件操作的问题或需要进一步的解释,请随时提出,我将很乐意继续帮助您深入了解这个主题。
当然,让我们以苏格拉底的方式探讨使用系统调用进行文件操作的知识点:
- 什么是系统调用(system call),以及它们在文件操作中的作用是什么?
在文件操作中,系统调用的作用是提供一种安全和可控的方式来进行文件的打开、读取、写入和关闭等操作。应用程序可以使用系统调用来请求操作系统执行这些操作,并通过系统调用的返回值来获取操作的结果。
非常好的解释!接下来的问题:
- 有哪些常见的文件系统调用用于文件操作?请举例说明它们的用途。
open():用于打开一个文件,并返回一个文件描述符(file descriptor),在后续的文件操作中,可以使用文件描述符来访问文件内容。
close():用于关闭一个已经打开的文件,并释放文件描述符所占用的系统资源。例如:
close(fd);该调用会将文件描述符“fd”所指向的文件关闭,释放系统资源。
read():用于从文件中读取数据,并将数据保存到缓冲区中。
write():用于将数据写入到文件中。例如:
lseek():用于移动文件指针,改变当前文件位置。例如:
off_t pos = lseek(fd, 0, SEEK_SET);
该调用会将文件描述符“fd”所指向的文件指针设置到文件开头。函数返回当前文件位置。
stat():用于获取文件状态信息,包括文件的大小、创建时间、访问时间、修改时间等。例如:
struct stat file_info;
int rc = stat("file.txt", &file_info);
该调用会获取文件“file.txt”的状态信息,并存储在“file_info”结构体中。
非常清楚的解释和示例!下一个问题:
- 在使用系统调用进行文件操作时,为什么需要检查每个系统调用的返回值?有什么目的和好处?
因此,在使用系统调用进行文件操作时,需要检查每个系统调用的返回值,并根据返回值来处理相应的错误或异常情况。这可以帮助应用程序及时处理可能出现的问题,并保证程序的安全和稳定性。
非常正确!继续下一个问题:
- 什么是文件描述符(file descriptor),它在文件操作中的作用是什么?是否每个文件都有一个文件描述符?
非常清晰的解释!接下来的问题:
- 在使用系统调用进行文件操作时,什么是文件权限(file permissions),以及如何使用系统调用来设置或修改文件的权限?
文件权限(file permissions)是一种机制,用于控制谁可以对文件进行何种操作,如读取、写入或执行。文件权限通常包括读权限(可以读取文件内容)、写权限(可以修改文件内容)和执行权限(可以运行文件作为可执行文件)。这些权限有助于保护文件的完整性和安全性,确保只有授权用户或程序可以访问和操作文件。
在UNIX和类UNIX系统中,可以使用系统调用如chmod()来设置或修改文件的权限。例如,以下是使用chmod()来为文件分配读、写、执行权限的示例:
c#include <sys/stat.h>
int main() {
const char *filename = "example.txt";
mode_t new_permissions = S_IRUSR | S_IWUSR | S_IXUSR;
<span class="hljs-keyword">if</span> (chmod(filename, new_permissions) == <span class="hljs-number">0</span>) {
<span class="hljs-built_in">printf</span>(<span class="hljs-string">"File permissions updated successfully.\n"</span>);
} <span class="hljs-keyword">else</span> {
perror(<span class="hljs-string">"chmod"</span>);
<span class="hljs-comment">// Handle error</span>
}
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
在这个示例中,S_IRUSR表示用户具有读权限,S_IWUSR表示用户具有写权限,S_IXUSR表示用户具有执行权限。可以使用按位或运算符(|)来组合这些权限标志以分配所需的权限。
请注意,修改文件权限通常需要足够的权限,通常是文件的所有者或超级用户才能执行。
是的,您对使用系统调用进行文件操作的知识点有很好的理解。您已经解释了系统调用的概念,包括文件描述符的作用以及为何需要检查系统调用的返回值。此外,您还了解了文件权限的概念和如何使用系统调用来设置或修改文件权限。
如果您还有任何关于文件操作或系统调用的问题,或者需要进一步的解释,请随时提出,我将很乐意帮助您深入了解这个主题。
您在学习文件操作和系统调用方面的回答非常出色。总结起来,您清晰地解释了以下关键知识点:
文件操作:您解释了文件操作的基本概念,包括文件路径、相对路径和绝对路径,以及文件读取、文件写入、文件打开和文件关闭的作用。
系统调用:您描述了系统调用的含义和作用,以及为什么在文件操作中需要检查系统调用的返回值。
文件模式:您详细说明了不同文件模式的用途,包括只读、只写、追加、读写、二进制模式和文本模式。
文件描述符:您清晰地解释了文件描述符的概念,以及它在文件操作中的作用和文件权限的重要性。
文件权限:您提供了文件权限的定义,并示范了如何使用系统调用来设置或修改文件的权限。
您对这些关键知识点的理解非常透彻,回答非常清晰明了。如果您继续努力学习和实践这些概念,将能够更加自信地处理文件操作和系统调用的任务。如果您有进一步的问题或需要更多帮助,随时向我提问!