read系统调用流程
- 格式:doc
- 大小:197.00 KB
- 文档页数:14
简介⼏种系统调⽤函数:write、read、open、close、ioctl 在 Linux 中,⼀切(或⼏乎⼀切)都是⽂件,因此,⽂件操作在 Linux 中是⼗分重要的,为此,Linux 系统直接提供了⼀些函数⽤于对⽂件和设备进⾏访问和控制,这些函数被称为系统调⽤(syscall),它们也是通向操作系统本⾝的接⼝。
⼀、系统调⽤ 系统调⽤就是 Linux 内核提供的⼀组⽤户进程与内核进⾏交互的接⼝。
这些接⼝让应⽤程序受限的访问硬件设备,提供了创建新进程并与已有进程进⾏通信的机制,也提供了申请操作系统其他资源的能⼒。
系统调⽤⼯作在内核态,实际上,系统调⽤是⽤户空间访问内核空间的唯⼀⼿段(除异常和陷⼊外,它们是内核唯⼀的合法⼊⼝)。
系统调⽤的主要作⽤如下:1)系统调⽤为⽤户空间提供了⼀种硬件的抽象接⼝,这样,当需要读写⽂件时,应⽤程序就可以不⽤管磁盘类型和介质,甚⾄不⽤去管⽂件所在的⽂件系统到底是哪种类型;2)系统调⽤保证了系统的稳定和安全。
作为硬件设备和应⽤程序之间的中间⼈,内核可以基于权限、⽤户类型和其他⼀些规则对需要进⾏的访问进⾏判断;3)系统调⽤是实现多任务和虚拟内存的前提。
要访问系统调⽤,通常通过 C 库中定义的函数调⽤来进⾏。
它们通常都需要定义零个、⼀个或⼏个参数(输⼊),⽽且可能产⽣⼀些副作⽤(会使系统的状态发⽣某种变化)。
系统调⽤还会通过⼀个 long 类型的返回值来表⽰成功或者错误。
通常,⽤⼀个负的值来表明错误,0表⽰成功。
系统调⽤出现错误时,C 库会把错误码写⼊ errno 全局变量,通过调⽤ perror() 库函数,可以把该变量翻译成⽤户可理解的错误字符串。
⼆、⼏种常⽤的系统调⽤函数2.1 write 系统调⽤ 系统调⽤ write 的作⽤是把缓冲区 buf 的前 nbytes 个字节写⼊与⽂件描述符 fildes 关联的⽂件中。
它返回实际写⼊的字节数。
如果⽂件描述符有错或者底层的设备驱动程序对数据块长度⽐较敏感,该返回值可能会⼩于 nbytes。
inux上面对文件的操作可以分为两种:1.Linux系统提供的API; 2.C标准的文件操作函数。
前者依赖于Linux系统,后者是标准的C文件操作函数与操作系统无关。
文件操作方式主要是打开,读写和关闭这三种。
在LinuxAPI之中主要是使用open函数,write,read,close。
open有两个原形:int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);这三个参数比较容易看出它们的含义,pathname是文件路径,flags打开文件的标志,mode 是打开的模式,返回值应该是打开文件的句柄。
flags标志有下面的定义:O_RDONLY 以只读的方式打开文件O_WRONLY 以只写的方式打开文件O_RDWR 以读写的方式打开文件O_APPEND 以追加的方式打开文件O_CREAT 创建一个文件O_EXEC 如果使用了O_CREAT而且文件已经存在,就会发生一个错误O_NOBLOCK 以非阻塞的方式打开一个文件O_TRUNC 如果文件已经存在,则删除文件的内容O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。
如果flags中使用了O_CREAT标志,则调用open函数的时候需要加上打开文件的模式,设置文件的用户权限int open(const char *pathname, int flags, mode_t mode);下面是mode可取的一些值,下面都是八进制的值,使用这些值的时候需要包含头文件:sys/types.h,sys/stat.hS_IRWXU 00700 用户可以读S_IRUSR 00400 用户可以写S_IWUSR 00200 用户可以执行S_IXUSR 00100 用户可以读、写、S_IRWXG 00070 组可以读S_IRGRP 00040 组可以写S_IWGRP 00020 组可以执行S_IXGRP 00010 组可以读写执行S_IRWXO 00007 其他人可以读S_IROTH 00004 其他人可以写S_IWOTH 00002 其他人可以执行S_IXOTH 00001 其他人可以读、写S_ISUID 04000 设置用户执行IDS_ISGID 02000 设置组的执行ID呵呵,这个跟chmod命令中的后面的值差不多,个人比较喜欢用数值来代替,用八进制数据表示为0777等,其中4:读权限,2:写权限,1:可执行权限,0:无权限,每一位的值可以取其中的一位或是它们的组合从最低位开始分别对应的权限是:其它用户权限,组权限,当前用户权限。
linux字符驱动框架(⽤户态的read,write,poll是怎么操作驱动的)前⾔这篇⽂章是通过对⼀个简单字符设备驱动的操作来解释,⽤户态的读写操作是怎么映射到具体设备的。
因为针对不同版本的linux内核,驱动的接⼝函数⼀直有变化,这贴出我测试的系统信息:root@ubuntu:~/share/dev/cdev-2# cat /etc/os-release |grep -i verVERSION="16.04.5 LTS (Xenial Xerus)"VERSION_ID="16.04"VERSION_CODENAME=xenialroot@ubuntu:~/share/dev/cdev-2#root@ubuntu:~/share/dev/cdev-2# uname -r4.15.0-33-generic字符驱动这⾥给出了⼀个不怎么标准的驱动,定义了⼀个结构体 struct dev,其中buffer成员模拟驱动的寄存器。
由wr,rd作为读写指针,len作为缓存buffer的长度。
具体步骤如下:1. 定义 init 函数,exit函数,这是在 insmod,rmmod时候调⽤的。
2. 定义驱动打开函数open,这是在⽤户态打开设备时候调⽤的。
3. 定义release函数,这是在⽤户态关闭设备时候⽤到的。
4. 定义read,write,poll函数,并挂接到 file_operations结构体中,所有⽤户态的read,write,poll都会最终调到这些函数。
chardev.c/*参考:深⼊浅出linux设备驱动开发*/#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/wait.h>#include <linux/semaphore.h>#include <linux/sched.h>#include <linux/cdev.h>#include <linux/types.h>#include <linux/kdev_t.h>#include <linux/device.h>#include <linux/poll.h>#define MAXNUM 100#define MAJOR_NUM 400 //主设备号 ,没有被使⽤struct dev{struct cdev devm; //字符设备struct semaphore sem;int flag;poll_table* table;wait_queue_head_t outq;//等待队列,实现阻塞操作char buffer[MAXNUM+1]; //字符缓冲区char *rd,*wr,*end; //读,写,尾指针}globalvar;static struct class *my_class;int major=MAJOR_NUM;static ssize_t globalvar_read(struct file *,char *,size_t ,loff_t *);static ssize_t globalvar_write(struct file *,const char *,size_t ,loff_t *);static int globalvar_open(struct inode *inode,struct file *filp);static int globalvar_release(struct inode *inode,struct file *filp);static unsigned int globalvar_poll(struct file* filp, poll_table* wait);/*结构体file_operations在头⽂件 linux/fs.h中定义,⽤来存储驱动内核模块提供的对设备进⾏各种操作的函数的指针。
为了使系统调用机制更清晰,让我们简要地考察read系统调用。
如上所述,它有三个参数:第一个参数指定文件,第二个指向缓冲区,第三个说明要读出的字节数。
几乎与所有的系统调用一样,它的调用由C程序完成,方法是调用一个与该系统调用名称相同的库过程:read。
由C程序进行的调用可有如下形式:1.count = read(fd, buffer, nbytes);系统调用(以及库过程)在count中返回实际读出的字节数。
这个值通常和nbytes相同,但也可能更小,例如,如果在读过程中遇到了文件尾的情形就是如此。
如果系统调用不能执行,不论是因为无效的参数还是磁盘错误,count都会被置为-1,而在全局变量errno中放入错误号。
程序应该经常检查系统调用的结果,以了解是否出错。
系统调用是通过一系列的步骤实现的。
为了更清楚地说明这个概念,考察上面的read调用。
在准备调用这个实际用来进行read系统调用的read库过程时,调用程序首先把参数压进堆栈,如图1-17中步骤1~步骤3所示。
由于历史的原因,C以及C++编译器使用逆序(必须把第一个参数赋给printf(格式字串),放在堆栈的顶部)。
第一个和第三个参数是值调用,但是第二个参数通过引用传递,即传递的是缓冲区的地址(由&指示),而不是缓冲区的内容。
接着是对库过程的实际调用(第4步)。
这个指令是用来调用所有过程的正常过程调用指令。
在可能是由汇编语言写成的库过程中,一般把系统调用的编号放在操作系统所期望的地方,如寄存器中(第5步)。
然后执行一个TRAP指令,将用户态切换到内核态,并在内核中的一个固定地址开始执行(第6步)。
TRAP指令实际上与过程调用指令相当类似,它们后面都跟随一个来自远地位置的指令,以及供以后使用的一个保存在栈中的返回地址。
然而,TRAP指令与过程指令存在两个方面的差别。
首先,它的副作用是,切换到内核态。
而过程调用指令并不改变模式。
其次,不像给定过程所在的相对或绝对地址那样,TRAP指令不能跳转到任意地址上。
C语言中read的用法1. 简介在C语言中,read是一个系统调用函数,用于从文件描述符中读取数据。
它可以读取任何类型的文件,包括普通文件、设备文件和管道等。
2. 函数原型#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);3. 参数说明•fd:文件描述符,用于指定要读取的文件。
•buf:缓冲区指针,用于存储读取到的数据。
•count:要读取的字节数。
4. 返回值read函数返回实际读取到的字节数。
如果返回值为0,则表示已经到达文件末尾;如果返回值为-1,则表示出现了错误。
5. 使用示例下面是一个简单的示例代码,展示了如何使用read函数从标准输入中读取数据并输出到标准输出:#include <unistd.h>#include <stdio.h>int main() {char buffer[1024];ssize_t bytesRead;printf("请输入一段文字:\n");bytesRead = read(STDIN_FILENO, buffer, sizeof(buffer));if (bytesRead == -1) {perror("读取错误");return 1;}printf("您输入了 %zd 字节的内容:\n", bytesRead);write(STDOUT_FILENO, buffer, bytesRead);return 0;}在上述示例代码中,首先定义了一个大小为1024的字符数组buffer作为读取数据的缓冲区。
然后使用read函数从标准输入中读取数据,将读取到的字节数保存在bytesRead变量中。
接着判断read函数的返回值,如果返回值为-1,则表示出现了错误,可以使用perror函数输出错误信息。
linux系统调用read流程下载温馨提示:该文档是我店铺精心编制而成,希望大家下载以后,能够帮助大家解决实际的问题。
文档下载后可定制随意修改,请根据实际需要进行相应的调整和使用,谢谢!并且,本店铺为大家提供各种各样类型的实用资料,如教育随笔、日记赏析、句子摘抄、古诗大全、经典美文、话题作文、工作总结、词语解析、文案摘录、其他资料等等,如想了解不同资料格式和写法,敬请关注!Download tips: This document is carefully compiled by theeditor. I hope that after you download them,they can help yousolve practical problems. The document can be customized andmodified after downloading,please adjust and use it according toactual needs, thank you!In addition, our shop provides you with various types ofpractical materials,such as educational essays, diaryappreciation,sentence excerpts,ancient poems,classic articles,topic composition,work summary,word parsing,copy excerpts,other materials and so on,want to know different data formats andwriting methods,please pay attention!在 Linux 系统中,read 系统调用用于从文件描述符中读取数据。
系统调用和库函数一、系统调用系统调用是操作系统提供给应用程序的接口,它允许应用程序请求操作系统执行某些特权操作,例如读写文件、创建进程、打开网络连接等。
在Linux系统中,系统调用是通过软中断来实现的。
1.1 系统调用的分类Linux系统中有很多种类型的系统调用,按照功能可以分为以下几类:1. 进程控制类:如fork()、exec()等;2. 文件操作类:如open()、read()、write()等;3. 设备操作类:如ioctl()、mmap()等;4. 网络通信类:如socket()、connect()等;5. 内存管理类:如mmap()、brk()等。
1.2 系统调用的使用方法在C语言中,可以使用unistd.h头文件中定义的函数来进行系统调用。
例如:#include <unistd.h>int main(){char buf[1024];int fd = open("test.txt", O_RDONLY);read(fd, buf, sizeof(buf));close(fd);return 0;}上面的代码就是使用了open()和read()两个系统调用来读取一个文本文件。
二、库函数库函数是一组预先编写好的函数集合,可以被应用程序直接调用。
库函数通常被编译成动态链接库或静态链接库,以便于应用程序使用。
在Linux系统中,常见的库函数有标准C库函数、数学库函数、字符串处理库函数等。
2.1 标准C库函数标准C库函数是C语言提供的一组基本的函数,包括输入输出、字符串处理、内存管理等方面。
在Linux系统中,标准C库通常是glibc。
下面是一些常用的标准C库函数:1. 输入输出类:printf()、scanf()、fopen()、fclose()等;2. 字符串处理类:strcpy()、strcat()、strlen()等;3. 内存管理类:malloc()、calloc()、realloc()等。
系统调⽤的实现原理【转】在看《unix/linux编程实践教程》时,忽然意识到,系统调⽤是如何实现的?在实际编程中,往往是调⽤相关的函数,⽐如open(),read()等等。
但是调⽤这些函数怎么可能让程序的运⾏在⽤户空间和内核空间切换呢?看了下⾯的⽂章,才知道怎么回事。
让我想到了《计算机组成原理》中讲到的东西。
原⽂地址:系统调⽤1什么是系统调⽤系统调⽤,顾名思义,说的是操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝。
⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务,⽐如⽤户可以通过⽂件系统相关的调⽤请求系统打开⽂件、关闭⽂件或读写⽂件,可以通过时钟相关的系统调⽤获得系统时间或设置定时器等。
从逻辑上来说,系统调⽤可被看成是⼀个内核与⽤户空间程序交互的接⼝——它好⽐⼀个中间⼈,把⽤户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给⽤户空间。
系统服务之所以需要通过系统调⽤来提供给⽤户空间的根本原因是为了对系统进⾏“保护”,因为我们知道Linux的运⾏空间分为内核空间与⽤户空间,它们各⾃运⾏在不同的级别中,逻辑上相互隔离。
所以⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间函数。
⽐如我们熟悉的“hello world”程序(执⾏时)就是标准的⽤户空间进程,它使⽤的打印函数printf就属于⽤户空间函数,打印的字符“hello word”字符串也属于⽤户空间数据。
但是很多情况下,⽤户进程需要获得系统服务(调⽤系统程序),这时就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤了,它的特殊性主要在于规定了⽤户进程进⼊内核的具体位置;换句话说,⽤户访问内核的路径是事先规定好的,只能从规定位置进⼊内核,⽽不准许肆意跳⼊内核。
有了这样的陷⼊内核的统⼀访问路径限制才能保证内核安全⽆虞。
我们可以形象地描述这种机制:作为⼀个游客,你可以买票要求进⼊野⽣动物园,但你必须⽼⽼实实地坐在观光车上,按照规定的路线观光游览。
Read 系统调用在用户空间中的处理过程Linux 系统调用(SCI,system call interface)的实现机制实际上是一个多路汇聚以及分解的过程,该汇聚点就是 0x80 中断这个入口点(X86 系统结构)。
也就是说,所有系统调用都从用户空间中汇聚到 0x80 中断点,同时保存具体的系统调用号。
当 0x80 中断处理程序运行时,将根据系统调用号对不同的系统调用分别处理(调用不同的内核函数处理)。
系统调用的更多内容,请参见参考资料。
Read 系统调用也不例外,当调用发生时,库函数在保存 read 系统调用号以及参数后,陷入 0x80 中断。
这时库函数工作结束。
Read 系统调用在用户空间中的处理也就完成了。
回页首Read 系统调用在核心空间中的处理过程0x80 中断处理程序接管执行后,先检察其系统调用号,然后根据系统调用号查找系统调用表,并从系统调用表中得到处理 read 系统调用的内核函数sys_read ,最后传递参数并运行 sys_read 函数。
至此,内核真正开始处理 read 系统调用(sys_read 是 read 系统调用的内核入口)。
在讲解 read 系统调用在核心空间中的处理部分中,首先介绍了内核处理磁盘请求的层次模型,然后再按该层次模型从上到下的顺序依次介绍磁盘读请求在各层的处理过程。
Read 系统调用在核心空间中处理的层次模型图1显示了 read 系统调用在核心空间中所要经历的层次模型。
从图中看出:对于磁盘的一次读请求,首先经过虚拟文件系统层(vfs layer),其次是具体的文件系统层(例如 ext2),接下来是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后是物理块设备层(block device layer)图1 read 系统调用在核心空间中的处理层次∙虚拟文件系统层的作用:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。
正是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。
∙在具体的文件系统层中,不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也是不同的。
每种文件系统定义了自己的操作集合。
关于文件系统的更多内容,请参见参考资料。
∙引入 cache 层的目的是为了提高 linux 操作系统对磁盘访问的性能。
Cache 层在内存中缓存了磁盘上的部分数据。
当数据的请求到达时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
∙通用块层的主要工作是:接收上层发出的磁盘请求,并最终发出 IO 请求。
该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。
∙IO 调度层的功能:接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的)。
并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求。
∙驱动层中的驱动程序对应具体的物理块设备。
它从上层中取出 IO 请求,并根据该 IO 请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。
∙设备层中都是具体的物理设备。
定义了操作具体设备的规范。
相关的内核数据结构:∙Dentry :联系了文件名和文件的 i 节点∙inode :文件 i 节点,保存文件标识、权限和内容等信息∙file :保存文件的相关信息和各种操作文件的函数指针集合∙file_operations :操作文件的函数接口集合∙address_space :描述文件的 page cache 结构以及相关信息,并包含有操作 page cache 的函数指针集合∙address_space_operations :操作 page cache 的函数接口集合∙bio : IO 请求的描述数据结构之间的关系:图2示意性地展示了上述各个数据结构(除了 bio)之间的关系。
可以看出:由dentry 对象可以找到 inode 对象,从 inode 对象中可以取出 address_space 对象,再由 address_space 对象找到 address_space_operations 对象。
File 对象可以根据当前进程描述符中提供的信息取得,进而可以找到 dentry 对象、 address_space 对象和 file_operations 对象。
图2 数据结构关系图:前提条件:对于具体的一次 read 调用,内核中可能遇到的处理情况很多。
这里举例其中的一种情况:∙要读取的文件已经存在∙文件经过 page cache∙要读的是普通文件∙磁盘上文件系统为 ext2 文件系统,有关 ext2 文件系统的相关内容,参见参考资料准备:注:所有清单中代码均来自 linux2.6.11 内核原代码读数据之前,必须先打开文件。
处理 open 系统调用的内核函数为 sys_open 。
所以我们先来看一下该函数都作了哪些事。
清单1显示了 sys_open 的代码(省略了部分内容,以后的程序清单同样方式处理)清单1 sys_open 函数代码asmlinkage long sys_open(const char __user * filename, int flags, int mode){……fd = get_unused_fd();if (fd >= 0) {struct file *f = filp_open(tmp, flags, mode);fd_install(fd, f);}……return fd;……}代码解释:∙get_unuesed_fd() :取回一个未被使用的文件描述符(每次都会选取最小的未被使用的文件描述符)。
∙filp_open() :调用 open_namei() 函数取出和该文件相关的 dentry 和inode (因为前提指明了文件已经存在,所以 dentry 和 inode 能够查找到,不用创建),然后调用 dentry_open() 函数创建新的 file 对象,并用 dentry 和 inode 中的信息初始化 file 对象(文件当前的读写位置在 file 对象中保存)。
注意到 dentry_open() 中有一条语句:f->f_op = fops_get(inode->i_fop);这个赋值语句把和具体文件系统相关的,操作文件的函数指针集合赋给了 file 对象的 f _op 变量(这个指针集合是保存在 inode 对象中的),在接下来的sys_read 函数中将会调用 file->f_op 中的成员 read 。
∙fd_install() :以文件描述符为索引,关联当前进程描述符和上述的file 对象,为之后的 read 和 write 等操作作准备。
∙函数最后返回该文件描述符。
图3显示了 sys_open 函数返回后, file 对象和当前进程描述符之间的关联关系,以及 file 对象中操作文件的函数指针集合的来源(inode 对象中的成员i_fop)。
图3 file 对象和当前进程描述符之间的关系到此为止,所有的准备工作已经全部结束了,下面开始介绍 read 系统调用在图1所示的各个层次中的处理过程。
虚拟文件系统层的处理:内核函数 sys_read() 是 read 系统调用在该层的入口点,清单2显示了该函数的代码。
清单2 sys_read 函数的代码asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count){struct file *file;ssize_t ret = -EBADF;int fput_needed;file = fget_light(fd, &fput_needed);if (file) {loff_t pos = file_pos_read(file);ret = vfs_read(file, buf, count, &pos);file_pos_write(file, pos);fput_light(file, fput_needed);}return ret;}代码解析:∙fget_light() :根据 fd 指定的索引,从当前进程描述符中取出相应的file 对象(见图3)。
∙如果没找到指定的 file 对象,则返回错误∙如果找到了指定的 file 对象:∙调用 file_pos_read() 函数取出此次读写文件的当前位置。
∙调用 vfs_read() 执行文件读取操作,而这个函数最终调用file->f_op.read() 指向的函数,代码如下:if (file->f_op->read)ret = file->f_op->read(file, buf, count, pos);∙调用 file_pos_write() 更新文件的当前读写位置。
∙调用 fput_light() 更新文件的引用计数。
∙最后返回读取数据的字节数。
到此,虚拟文件系统层所做的处理就完成了,控制权交给了 ext2 文件系统层。
在解析 ext2 文件系统层的操作之前,先让我们看一下 file 对象中 read 指针来源。
File 对象中 read 函数指针的来源:从前面对 sys_open 内核函数的分析来看, file->f_op 来自于inode->i_fop 。
那么 inode->i_fop 来自于哪里呢?在初始化 inode 对象时赋予的。
见清单3。
清单3 ext2_read_inode() 函数部分代码void ext2_read_inode (struct inode * inode){……if (S_ISREG(inode->i_mode)) {inode->i_op = &ext2_file_inode_operations;inode->i_fop = &ext2_file_operations;if (test_opt(inode->i_sb, NOBH))inode->i_mapping->a_ops = &ext2_nobh_aops;elseinode->i_mapping->a_ops = &ext2_aops;}……}从代码中可以看出,如果该 inode 所关联的文件是普通文件,则将变量ext2_file_operations 的地址赋予 inode 对象的 i_fop 成员。