您的位置:首页 > 运维架构

Open、Read、Write、Lseek、Close、Create、Delete、chdir系统调用

2014-12-03 13:56 489 查看

13.4.7 "Open(打开)"系统调用

当进程想要对文件执行任一操作时,它先要打开文件。该系统调用格式如下:

fd = Open (pathname, mode, flags, permissions),其中:

fd(文件描述符),表示文件描述符。pathname(路径名)表示要打开的文件的路径名。mode(模式)指的是打开文件的模式(例如,读、写)。flag(标识)表示指明创建文件的指示符(如果该文件不存在)或是产生的错误。permissions(权限)指的是如果创建文件,分配给该文件的访问权限。

"fd"指的是前面提到的在UFDT表中的文件描述符条目号。"open"系统调用在UFDT中生成条目。UFDT条目号或"fd"是由"open"系统调用返回的,以备将来使用。对所有文件进行读、写、查找或关闭这样进一步的操作时,都要指明这个"fd"。这样,内核可以将其作为UFDT的索引使用,从而遍历FT和IT数据结构。

路径名、模式等是打开系统调用时的输入参数。内核在"打开"文件时,执行以下动作:

(1) 内核解析由其中一个参数指定的路径名。如果路径名不存在,内核检查参数中的标识。如果不是创建新文件,内核就输出错误消息并退出。否则,它调用系统调用创建该文件。该系统调用会为新文件分配索引节点,并初始化索引节点的各个字段。这种情况下,它根据"permissions"参数在打开前设置要创建的文件的索引节点中存取控制位(rwx)。最终,内核检索到要打开的文件在磁盘上的索引节点号,而不管文件在创建前就已经存在或是需要创建。

(2) 内核在内存IT条目中搜索该索引节点,如图13-21所示。如果没有找到(如果正在创建新文件,或是第一次打开这个文件,就会这样),那么将磁盘上这个索引节点复制到IT条目中。这是在获取核心IT表中空闲索引节点以及将其从该表空闲条目链表中解除链接后完成的。

(3) 内核现在为该文件检查核心索引节点中的权限位。它验证想要打开文件的进程是否可以按照期望的模式处理(如果进程在"打开"的时候系统正在创建这个文件,必然是允许访问的)。如果拒绝访问,那么内核就输出错误消息并退出。

(4) 如果同意访问,而且如果对应索引节点的核心IT条目之前并不存在,那么内核就会初始化该索引节点在IT中的其他字段,如表13-2所示。

(5) 现在内核得到空闲的FT条目,将其从FT中空闲条目链表中解除链接,并设置获取的FT条目中如下字段:

模式(mode):从系统调用参数获取模式。

偏移量(offset):通常该值是0。这样,读/写可以从开始的位置进行。然而,如果文件是用"写-附加"模式打开的,该字段就被设为文件大小,这样任何要写入文件的内容就会附加到文件中。

计数(Count):1

指针(Pointer):该指针指向索引节点的IT条目。

(f) 现在得到UFDT中的下一个空闲条目,UFDT是打开文件的进程u区的一部分。因此,这个表格在创建进程之后就已经存在了。这个"下一个空闲条目"被称作"文件描述符(fd)"。内核存储"fd"并生成一个由UFDT条目指向FT中条目的指针。

(g) 内核将"fd"返回给该进程。

如果同一个进程用两种模式打开同一个文件:一个是读模式,另一个是写模式,那么算法几乎相同。这是因为,在这种情况下,核心索引节点已经存在,因此,不需要再从磁盘复制过来。算法可以保证这一点。此时,有两个UFDT条目,它们分别指向FT中两个不同的条目,但这两个FT只指向IT条目中唯一的条目,如图13-21所示。

13.4.8 "Read(读)"系统调用

"Read(读取)"系统调用格式如下

number= Read (fd, buffer, count),其中:

fd是文件描述符。buffer表示要读取的数据在内存中的起始地址。count表示要读的字节数。number表示执行该系统调用后实际读取的字节数。如果该系统调用失败,或是在读取所有字节前就到了文件结尾,那么该数值和"count" 字段中的参数值不同(否则,二者相同)。

应该注意:对文件而言,读操作是从该文件中对应模式(也就是读模式)下保存在FT条目中的偏移量开始的。内核按照以下步骤执行该系统调用:

(1) 内核将fd作为访问UFDT中正确条目的索引使用。

(2) 沿着由UFDT指向FT,再指向IT的指针访问正确的IT条目。诚如所见,内核在按照特定模式打开文件时设置所有这些条目。

(3) 内核验证用户对哪个进程提交了对该文件具有"读取"权限的系统调用。索引节点对所有这三类都包括读、写、执行权限。对用户类别而言,内核为用户设置访问权限。如果该用户被拒绝,那么就提示错误消息,并退出。前面已经研究过这部分内容,这里不再介绍。

(4) 现在,内核设置u区中的不同字段,从而消除将它们作为函数参数传递的必要性。这些参数如下:

模式(mode):读或写(本例中是读)

偏移量(offset):文件中从FT条目中开始偏移的字节

地址(address):目标内存起始地址(和系统调用参数一起提供)

指示器(indicator):指明目标内存在用户内存空间还是在内核内存空间

计数(count):要读取的字节数(和系统调用参数一起提供)

除了上述参数之外,几乎所有字段都是由系统调用的参数得到的。模式要从"读取"系统调用中得到,而偏移量要从FT中的条目得到。

(5) 内核现在锁定IT中的索引节点条目。对该文件进行的每个操作(写、关闭等)而言,内核都要查询IT中的该索引节点条目。因此,通过将该条目状态改为"被锁定",实际上任何进程对该文件的所有后续操作都会被挂起,这就确保了完整性和一致性。

(6) 现在内核读取请求的全部字节。除非在系统调用执行期间出现错误或是提前到了文件结尾处,否则就要读取全部的字节。该过程逐块地执行如下步骤:

内核将字节偏移量(也就是相对字节号RBN)转换成逻辑块号(LBN)以及LBN中的偏移量。

内核通过查询核心条目,将这个LBN转换成物理块号(PBN)。如果偏移量在前面10个逻辑块中(也就是偏移量小于10240),那么内核通过查询索引节点自身就可以得到PBN;否则要查询不同间接级别的索引节点。

内核将PBN转换成物理地址,并命令设备驱动程序先读取系统缓冲区中期望的块。该工作由DMA完成。

内核选择相关字节(取决于LBN中的偏移量)。对连续块而言,要选择大小为1024字节的整个块。

只要期望的字节小于等于"计数"字段中的数据,内核就会将期望的字节从系统缓冲区传送到内存缓冲区。

内核递增文件偏移量,并按照实际读取或传送的字节数递减计数字段。它还递增内存中目标起始地址,同时以相同的量递增"number"字段。

内核重复该过程,直到读取完毕或是出现错误或满足文件结束条件为止。

传送完所有期望的字节以及对应的"number"字段被更新后,内核输出这个"number"字段。该字段现在提供了实际读取的字节数。这个字段对于从读操作未完成的地方得到线程进行错误恢复很有用。

在读操作结束后,内核解锁IT中的索引节点条目。

问题在于:为什么在执行这个操作期间要锁定IT中的索引节点这个条目?原因在于确保一致性。进程可以调用一个"读取"系统调用,然后在这之间进入睡眠状态。如果允许另一个进程在第一个进程睡眠期间修改这个文件,那么"读取"系统调用得到的结果就不一致了。例如,可以有一个读取3个块或3072字节的系统调用。然而,已经知道实际的读操作是逐块进行的,整个系统调用并不是不可分的指令,因为实现这种不可分性,很难而且代价很大。

因此,该进程读完第一个块之后可能会睡眠,另一个进程可能会改变第二个块。这会产生不希望出现的不一致性。这就是为什么在适当的时候要将IT中索引节点条目锁定和解锁的原因。IT中"状态"字段有助于内核实现这样的处理工作。

同一个进程有可能打开同一个文件进行多次读取,通过不同的文件描述符读取该文件。这两个读操作通过两个不同的FT条目分别处理。

13.4.9 "Write(写入)"系统调用

"Write(写入)"系统调用格式如下

number= Write (fd, buffer, count),其中:

fd是文件描述符。buffer表示要写到文件的数据在内存中的起始地址。count表示要写入的字节数,number表示执行该系统调用后实际写入的字节数。通常,number的取值和count的取值相同,除非该系统调用失败。

这个系统调用和"Read"系统调用很相似。它的格式以及不同参数的含义以及该系统调用的实现也和"Read"系统调用的相似。因此,这里并不讨论细节内容。两者唯一差别列举如下:

如果某个块只有一部分内容要写,内核就要先读入该块,然后在将块写回磁盘前,将内存中要写的字节移动到块的合适位置,这样就不会覆盖现有数据,这些数据也不会丢失了。图13-22描述了该过程。

如果所有可分配给文件的块用完了,那么也许就要分配新块。这就有必要调用相关系统调用或算法,这些系统调用或算法获取空闲块或将这些空闲块加载到索引节点或间接索引结构,然后从空闲块链表中解除这些块的链接。对"读取"系统调用而言,这是不必要的。



图13-22 对部分块的写操作

13.4.10 随机查找--"Lseek"系统调用

读取和写入这两个系统调用允许从适当的FT条目中该文件的某个偏移位置读取或写入一定字节的数据。然而,UNIX要支持不同的在线数据库应用,这些应用程序需要从不同的偏移量或RBN读取记录。已经知道,与文件中该记录的RBN相比,RDBMS可以维护键的索引。给定一个键,RDBMS搜索这个索引表,提取RBN,然后向操作系统发送由偏移量m开始的位置读取n个字节的调用,这里n是记录长度,m是从索引表中提取出的RBN。

随机查找的系统调用语法格式如下所示:

position = Lseek (fd, offset, reference ),其中:

fd是文件描述符。offset是新的RBN。Reference指明是从文件开始处、当前位置还是结尾处的偏移量。据此,系统调用中的偏移量分别加上0,或是加上FT条目中的存在偏移量,或是从索引节点中保存的文件大小中减去这个值,然后将其写回到FT条目中。

该系统调用的执行不需要再做说明。它只改变FT条目中的偏移量。这之后,用户可以使用读取或写入系统调用,由这个偏移量开始读/写数据。在数据库应用程序中,如果该系统调用确定了具有提供了键值的特定记录RBN处的偏移量,那么从该位置处读取记录就会得到期望记录的具体内容。正是由于这个原因,该程序在数据库应用中很有用。

13.4.11 "Close(关闭)"系统调用

"Close(关闭)"系统调用关闭以前打开的文件。该系统调用的语法如下:

close (fd),其中fd表示文件描述符。

内核执行该系统调用如下:

① 内核以"fd"为索引访问UFDT中的条目。

② 内核使用指针遍历对应的FT条目。

③ 将FT条目中的计数字段递减1。如果计数因为"分支"或"复制"操作大于1,那么递减后的计数就不等于0。然后,内核退出系统调用,因为另一个UFDT条目也在访问同一个FT条目。

④ 如果计数在减1后变成0,那么内核释放该FT。

⑤ 在释放FT条目之前,内核从FT穿越到相应的索引节存储区IT条目,也将其中的计数字段减1。

⑥ 如果索引节存储区IT中的计数字段变成0,那么内核也释放IT中的索引节存储区索引节点,并将其链接到IT的空闲索引节存储区索引节点池中。

⑦ 最后,内核删除该文件在UFDT中的条目,不管对应的FT和IT是否被删除。进程中任何对该文件(也就是具有相同的"fd"号)的其他引用被当作是无效的,除非再次打开该文件,因为打开该文件会创建新的"fd"条目。

因此,如果同一个进程采用多种模式打开文件,那么就要对所有这些模式明确地关闭该文件。另一种方式就是,如果进程终止,内核可以检查它的所有UFDT条目,并针对所有模式逐个关闭文件。内核将所有数据结构中只有该进程打开的所有文件条目清除掉。其他条目继续保留,因为其他进程也许仍要用到它们。

13.4.12 "Create(创建)"系统调用

该系统调用语法如下:

fd=Create(pathname, permission),其中:

fd表示文件描述符。pathname表示要创建的文件的路径名。permission表示授予对该文件的访问权限。

为了执行该系统调用,内核解析路径名,通过执行前面介绍的路径名解析过程查找文件是否已经存在。如果该文件不存在,就创建该文件。如果该文件存在,那么截断该文件,释放该文件的全部数据块,并将文件大小设为0。从本质上讲,这个过程意味着内核创建具有相同文件名的文件。因此,要在相同目录中删除旧文件并创建新文件。内核按照以下步骤进行:

(1) 内核解析不同目录下的路径名,直到找到最终的文件名为止。内核为该目录保存索引节点(也就是在文件创建的目录),还包括文件名在内。

(2) 内核访问该目录的索引节点,并检查创建进程的用户类别对应的rwx位,确保进程可以向"目录"文件写内容,因为目录中新文件的创建会针对该目录创建或写入一个条目。如果没有访问权限,内核弹出错误消息并退出。

(3) 如果进程有访问权限,内核就按照分配给"目录文件"的数据块读取目录内容,目录文件由内核通过该目录文件的索引节点得到。

(4) 然后,内核搜索该目录中的文件名,检查是否有该文件。如果有,就进入以下步骤:

① 从目录中获取该文件的索引节点号。

② 获取文件的索引节点。

③ 将文件大小设为0(可以采用一种分配给该文件几个块(例如2个块),而不是分配0块的实现方式)。

遍历分配给该文件的所有直接块和间接块,并在这些块空闲的时候释放它们,将其添加到空闲块链表中。

进入步骤6(也就是跳过步骤5)。

(5) 如果文件名已经不存在,它会经历以下步骤:

① 搜索目录中空闲的条目,并将文件名存在其中。

② 从超级块提供的空闲索引节点列表中分配一个空闲索引节点,并解除它和空闲链表的链接。

③ 在目录文件中,按照步骤(1)中得到的空闲条目中文件名设置索引节点号。

(6) 内核在将索引节点写回到磁盘之前为该文件初始化索引节点条目中的各个字段。这个初始化工作按照以下内容完成:

所有者(Owner):创建者的用户名--从创建它的进程u区得到的

组Id(Group id):由u区得到的组id

类型(Type):普通文件(其他文件类型的系统调用格式不同)

权限(Permissions):由参数得到

创建的日期与时间(Date & time of creation):系统日期

其他日期(Other dates):空白

大小(Size):0

地址(Addresses):空白

在创建的时候,内核可以采用为文件分配一定数量的块(例如2个块)的UNIX实现方式。这种情况下,内核调用块分配算法,合理设置索引节点中直接块号条目和文件大小。

13.4.13 "Delete(删除)"系统调用

"Delete(删除)"系统调用类似于创建文件的"Create"系统调用,这里是删除文件而不是创建文件。该系统调用的语法格式如下:

delete (pathname),其中pathname表示要删除的文件的路径名。

采取下面的步骤执行该系统调用:

(1) 解析路径名。

(2) 存储目录索引节点号。

(3) 访问该目录的索引节点,确保进程实际更改了目录文件。该工作是通过检查访问权限位完成的。如果访问被拒绝,内核弹出错误消息并退出。

(4) 通过给定的目录索引节点读取目录的数据块而读出该目录的内容。

(5) 搜索文件名。如果不存在,内核弹出错误消息并退出。

(6) 如果找到文件,从该目录条目中提取出该文件的索引节点号,并使用该索引节点号访问实际的索引节点。

(7) 系统现在将索引节点中的"链接数(no. of links)"字段减1。如果该字段的值仍大于0,该算法除了修改"计数"字段外就不做任何工作。这是因为,还有其他用户使用这个文件。因此,就不能物理地删除该文件。然而,如果减1之后"链接数"字段变成0,那就表明没有用户使用这个文件。现在继续执行如下步骤:

① 系统检查索引节点中所有的数据块(0~9),以及其他由不同间接级(单级、两级、三级)索引给定编号的数据块,将所有数据块设为空闲,也就是将它们添加到空闲块链表中。

② 现在系统释放索引节点条目,并将其添加到空闲索引节点链表中。

③ 然后,系统不管索引节点是否空闲,都会删除目录中该文件的条目。这是因为相同路径名不再可以访问该文件。

13.4.14 "chdir(改变目录)"系统调用

改变目录的系统调用语法格式如下:

chdir (pathname),其中pathname就是成为进程当前新目录的目录。

用户登录系统后,系统将用户放置在用户自己的主目录中。主目录路径名由系统管理员在创建用户时保存在/etc/passwd文件中。用户登录系统时,该目录路径名被复制到进程的u区。然后,主目录成为用户当前目录。如果用户想要改变到新的目录,就要执行该系统调用。有一个名为cd的shell命令或实用程序,用户可以在终端执行该命令更改工作目录,该命令内部使用了相同的系统调用。

执行该系统调用后,以路径名为参数提供的新目录成为当前目录或工作目录。其执行过程如下:

(1) 内核解析参数中的路径名,并确保这个路径名有效。为了做到这一点,就路径名解析而言,内核使用相同的算法。如果路径名无效,它输出错误消息并退出。

(2) 如果路径名有效,内核定位该目录的索引节点,并检查它的文件类型和权限位,确保目标文件是目录以及进程的所有者可以访问该目录(否则改变到新目录就没有用)。

(3) 内核用新目标目录的路径名和/或索引节点替换u区中当前目录路径名和/或它的索引节点号。

shell还有一个"ls"命令,它可以列出当前目录中所有的文件和目录。这个当前目录由u区得到。因此,当逐个提交"cd"和"ls"命令时,就可以在更改后的目录中看到所有文件/目录的列表
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: