知识点归纳
一、文件操作
1.文件操作级别
文件操作分为五个级别,按照从低到高的顺序可以如下表示:
硬盘级别
这些操作多是针对系统的实用程序,一般用户不会涉及到它们的运用,但是这些操作是创建和维护系统的关键。这些操作包括:
1.fdisk: 将硬盘、U盘或者SDC盘分区
2.mkfs: 格式化磁盘分区,为系统做好准备
3.fsck: 检查和维修系统
4.碎片整理: 压缩系统中的文件
操作系统内核中的文件系统函数
每个操作系统内核都是可以为基本文件操作提供支持的。它们有一个共同点,就是有 k 前缀:
系统调用
用户模式用来访问内核函数的工具,在C语言中是一系列文件操作相关的库函数,如
open() => kopen()
read() => kread()
lseek()...
close()...
这些函数在第十章中有较为详实的使用解析。
内核函数传输数据是按数据块的格式进行的。
这些库函数会发出一个系统调用,使进程进入内核模式来执行相关的内核函数,以达到文件操作的目的。进程结束执行内核函数后就会回到用户模式。在内核模式下,每次读取的内容一般为 n KB的数据块,根据系统不同, n 的值会在1~8间变化 ,比如,在Linux中,硬盘默认的数据块大小为4KB,软盘为1KB。
I/O库函数
I/O库函数可以提供数据缓冲区,方便对数据按字符、行或者数据结构的形式进行读写,C语言中有许多常用的I/O库函数,比如 scanf() 等
一些基本格式:
文件模式: fopen() , fread() ; fwrite() ,fseek() ,fclose() ,fflush()
字符模式: getc() ,getchar() , ugetc() ,putc() ,putchar()
行模式: gets() ,fgets() ,puts() ,fputs()
数据结构模式: scanf() ,fscanf() ,sscanf() ,printf() ,fprintf() ,sprintf()
除了读/写内存位置的函数 sscanf()和sprintf()以外,其他的所有I/O库函数都是建立在系统调用之上的。
用户命令
在Linux系统中,会经常用到一些用户命令,如 mkdir ,ls ,mv ,cp等,除此自外还有如下用户命令
rmdir ,cd ,pwd ,link ,unlink ,rm ,cat ,chmod...
其中除了cd命令以外,其余都为一个可执行程序,这些程序通常会通过调用I/O库函数实现功能。
sh脚本
使用sh编程语言编写的一种脚本程序,虽然比系统调用方便,但是要手动输入命令写脚本,还要指定设备来进行输入。
2.文件I/O操作
下图为文件I/O操作示意图。
3.低级别文件操作
1.分区
像给一块大容量硬盘分区,可以通过分区操作来将一个大存储空间分为不同的逻辑单元,各个分区可以格式化成为特定的文件文件系统,可以有效的隔离不同数据。
在Linux系统下,可以通过如下操作创建一个虚拟磁盘映象文件:
dd if=/dev/zero of=disk20191314 bs=1024 count=1440
2.格式化分区
fdisk 将一个存储设备划分为多个分区,但是刚刚划分出来的分区还需要经过格式化才可以存储文件,可以使用mkfs命令在特定的分区上建立 linux 文件系统,mkfs的一般格式如下:
mkfs [-V] [-t fstype] [fs-options] filesys [blocks]
3.挂载分区
losetup 命令用于设置循环设备。循环设备可把文件虚拟成区块设备,籍以模拟整个文件系统,让用户得以将其视为硬盘驱动器,光驱或软驱等设备,并挂入当作目录来使用。 losetup命令语法如下:
losetup [-d][-e <加密方式>][-o <平移数目>][循环设备代号][文件]
4.EXT2文件系统简介
ext2是Linux系统所默认的文件系统
它有如下的磁盘块:
BLOCK#0: 引导块 用于容纳从磁盘引导操作系统的引导程序,不会被文件操作系统使用
BLOCK#1: 超级块 在硬盘分区中字节偏移量为1024,超级块容纳了关于整个文件系统的信息
超级块有一些重要字段,这些字段表明了超级块的各段含义:
BLOCK#2: 块组表述符块 ext2将磁盘块分成若干组,每组8192块(约32K),每组都有一个块组描述符结构体
BLOCK#8: 块位图 表示某种项的位序列
BLOCK#9: 索引节点位图 代表一个文件的数据结构
BLOCK#10: 索引节点 大小为128字节的索引结构体,表示一个文件
二、使用系统调用进行文件操作
常用的系统调用
stat 获取文件状态信息
int stat(char *filename ,struct stat *buf)
int fstat(char 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[ 1, int count)
write:写入打开的文件描述符
int write(int fd, char buf[ ], int count)
lseek:重新定位文件描述符的读/写偏移量
int 1seek(int fd, int offset, int whence)
dup:将文件描述符复制到可用的最小描述符编号中
int dup(int oldfd);
dup2:将oldfd复制到newfd中,如果newfd已打开,先将其关闭
int dup2(int oldfd, int newfd)
link:将新文件硬链接到旧文件
int link(char *oldPath, 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)
进程控制
1)getpid,getppid——获取进程识别号
include <sys/types.h>
include <unistd.h>
pid_t getpid(void);
获取进程标识号。这可以作为一个独一无二的临时文件名。
pid_t getppid(void);
获取父进程标示号。
2)fork——创建一个子进程
include <sys/types.h>
include <unistd.h>
pid_t fork(void);
通过完全复制当前进程创建一个新进程。
如果创建子进程成功,在父进程中返回子进程的进程标示符,在子进程中返回0。
如果失败,在父进程中返回-1,子进程不会被创建,errno被设置。
因为在fork()的调用处,整个父进程空间会原模原样地复制到子进程中,包括指令,
变量值,程序调用栈,环境变量,缓冲区,等等。所以,子进程和父进程是不能通过
程序内的变量(即使是全局变量)通信的,对于这两个进程来说,它们有各自的进程空间,
互不影响。但父进程和子进程可以通过管道,共享内存,等方式实现通信。
3)sleep——使进程睡眠指定的秒数
include <unistd.h>
unsigned int sleep(unsigned int seconds);
到达指定时间后返回0,若有信号中断,则返回剩余的秒数。
4)wait,waitpid——等待子进程终止
include <sys/types.h>
include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
等待子进程的状态发生变化,并且获得状态变化的信息。状态变化是指:子进程终止;子进程被信号停止;
或者子进程被信号恢复。
等待子进程终止可以让系统释放子进程的资源,如果不等待,那么就会产生僵尸进程。
如果一个子进程状态发生变化,系统调用立即返回,否则就一直阻塞,直到子进程状态发生变化,或者一个
信号中断系统调用。
wait(&status);等价于waitpid(-1, &status, 0);
wait等待一个子进程终止,waitpid等待pid指定的子进程状态改变。默认waitpid仅等待子进程终止,可以
通过options来改变行为。
wait执行成功返回终止子进程的进程号,否则返回-1.
waitpid执行成功返回状态改变子进程的进程号;如果指定了WHOHANG并且pid指定的子进程存在,但是
状态没有改变,立即返回0。错误返回-1.
子进程的返回状态由status存储,可以通过宏来获得信息。如果不关心,可以将status设成NULL。
5)execve——执行程序
include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
最有收获的地方
因为平时我所应用的操作系统就是Windows系列,所以我并没有体会到操作系统的原理。Windows几乎将所有的进程都通过窗口这个东西封装起来了,所以即使Windows很人性化,提高了使用性,但是却很难真正搞懂这其中的原理所在。
这两章都是针对于系统调用层面的文件操作,因为之前使用Linux系统输入过很多命令行,但都不懂这个作用,但是再教材上学到了有关这些命令行的程序,收获巨大。我最感兴趣的是ls程序。
ls 如此重要的原因在于它允许查看目录中的文件。将经常使用它来列出目录内容。 ls 不是一个复杂的命令,实现起来也很容易,但它确实包含许多不同的选项,可用于列出包含附加信息的文件。
在这过程中,你可能会发现其中一些选项非常有用,即使 ls 本身总是足以列出内容。掌握 ls 命令将使你更有效地列出目录内容和查找文件。它也可以在 Bash 脚本中使用,以帮助其他工具操作文件。
问题
怎么实现ls命令?
解决思路
我们已经通过在命令行上输入ls命令,列出目录中包含的文件和子目录。
-a 选项还将列出隐藏文件(名称以 . 开头的文件)。 除非您在根目录中,否则它还会列出 . (当前工作目录)和 … (向上一个目录)作为文件。
那么我们如何读取目录、文件信息呢?
最多常见的readdir使用方式:
include<stdio.h>
include<dirent.h>
include<string.h>
include <stdio.h>
include
include <stdlib.h>
using namespace std;
define FILE_NAME "/opt/code/linux_command_code"
int main(int argc, char **argv)
{
DIR *dir;
struct dirent *ptr;
dir = opendir(FILE_NAME);
if(NULL == dir)
{
cout << "opendir is NULL" << endl;
return -1;
}
while( (ptr = readdir(dir))!=NULL)
{
printf("d_ino:%ld d_off:%ld d_name: %s\n", ptr->d_ino,ptr->d_off,ptr->d_name);
}
closedir(dir);
return 0;
}
编译运行:
实践内容与截图,代码链接
实践内容我选择再分区范围内进行:
再Linux下,创建一个名为mydisk的虚拟磁盘映像文件。
dd if=/dev/zero of=mydisk bs=1024 count=1440
然后在磁盘上运行fdisk:
截图如下: