这些操作系统的概念,保你没听过!(二)

简介: 大部分操作系统提供了特定的基础概念和抽象,例如进程、地址空间、文件等,它们是需要理解的核心内容。下面我们会简要介绍一些基本概念,为了说明这些概念,我们会不时的从 UNIX 中提出示例,相同的示例也会存在于其他系统中,我们后面会进行介绍。

用于进程管理的系统调用

在 UNIX 中,fork 是唯一可以在 POSIX 中创建进程的途径,它创建一个原有进程的副本,包括所有的文件描述符、寄存器等内容。在 fork 之后,原有进程以及副本(父与子)就分开了。在 fork 过程中,所有的变量都有相同的值,虽然父进程的数据通过复制给子进程,但是后续对其中任何一个进程的修改不会影响到另外一个。fork 调用会返回一个值,在子进程中该值为 0 ,并且在父进程中等于子进程的 进程标识符(Process IDentified,PID)。使用返回的 PID,就可以看出来哪个是父进程和子进程。

微信图片_20220412210853.jpg

在多数情况下, 在 fork 之后,子进程需要执行和父进程不一样的代码。从终端读取命令,创建一个子进程,等待子进程执行命令,当子进程结束后再读取下一个输入的指令。为了等待子进程完成,父进程需要执行 waitpid 系统调用,父进程会等待直至子进程终止(若有多个子进程的话,则直至任何一个子进程终止)。waitpid 可以等待一个特定的子进程,或者通过将第一个参数设为 -1 的方式,等待任何一个比较老的子进程。当 waitpid 完成后,会将第二个参数 statloc 所指向的地址设置为子进程的退出状态(正常或异常终止以及退出值)。有各种可使用的选项,它们由第三个参数确定。例如,如果没有已经退出的子进程则立刻返回。

那么 shell 该如何使用 fork 呢?在键入一条命令后,shell 会调用 fork 命令创建一个新的进程。这个子进程会执行用户的指令。通过使用 execve 系统调用可以实现系统执行,这个系统调用会引起整个核心映像被一个文件所替代,该文件由第一个参数给定。下面是一个简化版的例子说明 fork、waitpid 和 execve 的使用

#define TRUE 1
while(TRUE){                                            /* 一直循环下去 */
    type_prompt();                                  /* 在屏幕上显示提示符 */
    read_command(command,parameters)                    /* 从终端读取输入 */
    if(fork() != 0){                                        /* fork 子进程 */
        /* 父代码 */
        waitpid(-1, &status, 0);                            /* 等待子进程执行完毕 */
    }else{
        /* 子代码 */
        execve(command,parameters,0)                    /* 执行命令 */
    }
}

一般情况下,execve 有三个参数:将要执行的文件名称,一个指向变量数组的指针,以及一个指向环境数组的指针。这里对这些参数做一个简要的说明。

先看一个 shell 指令

cp file1 file2

此命令把 file1 复制到 file2 文件中,在 shell 执行 fork 之后,子进程定位并执行文件拷贝,并将源文件和目标文件的名称传递给它。

cp 的主程序(以及包含其他大多数 C 程序的主程序)包含声明

main(argc,argv,envp)

其中 argc 是命令行中参数数目的计数,包括程序名称。对于上面的例子,argc 是3。第二个参数argv 是数组的指针。该数组的元素 i 是指向该命令行第 i 个字符串的指针。在上面的例子中,argv[0] 指向字符串 cp,argv[1] 指向字符串 file1,argv[2] 指向字符串 file2。main 的第三个参数是指向环境的指针,该环境是一个数组,含有 name = value 的赋值形式,用以将诸如终端类型以及根目录等信息传送给程序。这些变量通常用来确定用户希望如何完成特定的任务(例如,使用默认打印机)。在上面的例子中,没有环境参数传递给 execve ,所以环境变量是 0 ,所以 execve 的第三个参数为 0 。

可能你觉得 execve 过于复杂,这时候我要鼓励一下你,execve 可能是 POSIX 的全部系统调用中最复杂的一个了,其他都比较简单。作为一个简单的例子,我们再来看一下 exit ,这是进程在执行完成后应执行的系统调用。这个系统调用有一个参数,它的退出状态是 0 - 255 之间,它通过 waitpid 系统调用中的 statloc 返回给父级。

UNIX 中的进程将内存划分成三个部分:text segment,文本区,例如程序代码,data segment,数据区,例如变量,stack segment,栈区域。数据向上增长而堆栈向下增长,如下图所示

微信图片_20220412210901.png

上图能说明三个部分的内存分配情况,夹在中间的是空闲区,也就是未分配的区域,堆栈在需要时自动的挤压空闲区域,不过数据段的扩展是显示地通过系统调用 brk 进行的,在数据段扩充后,该系统调用指向一个新地址。但是,这个调用不是 POSIX 标准中定义的,对于存储器的动态分配,鼓励程序员使用 malloc 函数,而 malloc 的内部实现则不是一个适合标准化的主题,因为几乎没有程序员直接使用它。

用于文件管理的系统调用

许多系统调用都与文件系统有关,要读写一个文件,必须先将其打开。这个系统调用通过绝对路径名或指向工作目录的相对路径名指定要打开文件的名称,而代码 O_RDONLYO_WRONLYO_RDWR 的含义分别是只读、只写或者两者都可以,为了创建一个新文件,使用 O_CREATE 参数。然后可使用返回的文件描述符进行读写操作。接着,可以使用 close 关闭文件,这个调用使得文件描述符在后续的 open 中被再次使用。

最常用的调用还是 readwrite,我们再前面探讨过 read 调用,write 具有与 read 相同的参数。

尽管多数程序频繁的读写文件,但是仍有一些应用程序需要能够随机访问一个文件的任意部分。与每个文件相关的是一个指向文件当前位置的指针。在顺序读写时,该指针通常指向要读出(写入)的下一个字节。Iseek 调用可以改变该位置指针的值,这样后续的 read 或 write 调用就可以在文件的任何地方开始。

Iseek 有三个参数,position = iseek(fd,offset,whence),第一个是文件描述符,第二个是文件位置,第三个是说明该文件位置是相对于文件起始位置,当前位置还是文件的结尾。在修改了指针之后,Iseek 所返回的值是文件中的绝对位置。

UNIX 为每个文件保存了该文件的类型(普通文件、特殊文件、目录等)、大小,最后修改时间以及其他信息,程序可以通过 stat 系统调用查看这些信息。s = stat(name,&buf),第一个参数指定了被检查的文件;第二个参数是一个指针,该指针指向存放这些信息的结构。对于一个打开的文件而言,fstat 调用完成同样的工作。

用于目录管理的系统调用

下面我们探讨目录和整个文件系统的系统调用,上面探讨的是和某个文件有关的系统调用。mkdirrmdir 分别用于创建s = mkdir(nname,mode) 和删除 s = rmdir(name) 空目录,下一个调用是 s = link(name1,name2) 它的作用是允许同一个文件以两个或者多个名称出现,多数情况下是在不同的目录中使用 link ,下面我们探讨一下 link 是如何工作的

微信图片_20220412210906.png

图中有两个用户 astjim,每个用户都有他自己的一个目录和一些文件,如果 ast 要执行一个包含下面系统调用的应用程序

link("/usr/jim/memo", "/usr/ast/note");

jim 中的 memo 文件现在会进入到 ast 的目录中,在 note 名称下。此后,/usr/jim/memo/usr/ast/note 会有相同的名称。

用户目录是保存在 /usr,/user,/home 还是其他位置,都是由本地系统管理员决定的。

要理解 link 是如何工作的需要清楚 link 做了什么操作。UNIX 中的每个文件都有一个独一无二的版本,也称作 i - number,i-编号,它标示着不同文件的版本。这个 i - 编号是 i-nodes,i-节点 表的索引。每个文件都会表明谁拥有这个文件,这个磁盘块的位置在哪,等等。目录只是一个包含一组(i编号,ASCII名称)对应的文件。UNIX 中的第一个版本中,每个目录项都会有 16 个字节,2 个字节对应 i - 编号和 14 个字节对应其名称。现在需要一个更复杂的结构需要支持长文件名,但是从概念上讲一个目录仍是一系列(i-编号,ASCII 名称)的集合。在上图中,mail 的 i-编号为 16,依此类推。link 只是利用某个已有文件的 i-编号,创建一个新目录项(也许用一个新名称)。在上图 b 中,你会发现有两个相同的 70 i-编号的文件,因此它们需要有相同的文件。如果其中一个使用了 unlink 系统调用的话,其中一个会被移除,另一个将保留。如果两个文件都移除了,则 UNIX 会发现该文件不存在任何没有目录项(i-节点中的一个域记录着指向该文件的目录项),就会把该文件从磁盘中移除。

就像我们上面提到过的那样,mount 系统 s = mount(special,name,flag) 调用会将两个文件系统合并为一个。通常的情况是将根文件系统分布在硬盘(子)分区上,并将用户文件分布在另一个(子)分区上,该根文件系统包含常用命令的二进制(可执行)版本和其他使用频繁的文件。然后,用户就会插入可读取的 USB 硬盘。

通过执行 mount 系统调用,USB 文件系统可以被添加到根文件系统中,

微信图片_20220412210910.jpg

图 a 是安装前的系统文件,图 b 是安装后的系统文件。

如果用 C 语言来执行那就是

mount("/dev/sdb0","/mnt",0)

这里,第一个参数是 USB 驱动器 0 的块特殊文件名称,第二个参数是被安装在树中的位置,第三个参数说明将要安装的文件系统是可读写的还是只读的。

当不再需要一个文件系统时,可以使用 umount 移除之。

            </div>
目录
相关文章
|
1月前
|
消息中间件 存储 算法
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
82 0
|
2月前
|
存储 消息中间件 中间件
DP读书:《openEuler操作系统》(一)操作系统基本概念
DP读书:《openEuler操作系统》(一)操作系统基本概念
46 2
|
6月前
|
Linux Shell Go
《Linux操作系统编程》 第五章 文件和文件系统: 了解文件和文件系统的概念和特性,掌握Linux文件系统的基本操作
《Linux操作系统编程》 第五章 文件和文件系统: 了解文件和文件系统的概念和特性,掌握Linux文件系统的基本操作
69 0
|
6月前
|
安全 Unix Linux
《Linux操作系统编程》第三章 Linux操作系统基础: 了解Linux操作系统的相关概念
《Linux操作系统编程》第三章 Linux操作系统基础: 了解Linux操作系统的相关概念
96 0
|
3月前
|
Ubuntu Unix Linux
Linux 学习笔记一: 常见操作系统相关概念
Linux 学习笔记一: 常见操作系统相关概念
Linux 学习笔记一: 常见操作系统相关概念
|
3月前
|
程序员 C语言 iOS开发
CHS_01.1.1.1+1.1.3+操作系统的概念、功能
CHS_01.1.1.1+1.1.3+操作系统的概念、功能
|
4月前
【操作系统】—I/O设备的基本概念和分类
【操作系统】—I/O设备的基本概念和分类
|
4月前
|
存储 监控 算法
【操作系统】—处理机调度的概念以及层次
【操作系统】—处理机调度的概念以及层次
【操作系统】—处理机调度的概念以及层次
|
4月前
|
调度
【操作系统】—线程概念和多线程模型
【操作系统】—线程概念和多线程模型
|
4月前
|
调度 Windows
【操作系统】—操作系统的概念 目标和功能
【操作系统】—操作系统的概念 目标和功能