Linux下C语言的文件读写
ubuntuLinux下C语言open函数打开或创建文件与read,write函数详细讲解

ubuntuLinux下C语⾔open函数打开或创建⽂件与read,write函数详细讲解open(打开⽂件)相关函数read,write,fcntl,close,link,stat,umask,unlink,fopen表头⽂件#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>定义函数int open( const char * pathname, int flags);int open( const char * pathname,int flags, mode_t mode);函数说明参数pathname 指向欲打开的⽂件路径字符串。
下列是参数flags 所能使⽤的旗标:O_RDONLY 以只读⽅式打开⽂件O_WRONLY 以只写⽅式打开⽂件O_RDWR 以可读写⽅式打开⽂件。
上述三种旗标是互斥的,也就是不可同时使⽤,但可与下列的旗标利⽤OR(|)运算符组合。
O_CREAT 若欲打开的⽂件不存在则⾃动建⽴该⽂件。
O_EXCL 如果O_CREAT也被设置,此指令会去检查⽂件是否存在。
⽂件若不存在则建⽴该⽂件,否则将导致打开⽂件错误。
此外,若O_CREAT与O_EXCL同时设置,并且欲打开的⽂件为符号连接,则会打开⽂件失败。
O_NOCTTY 如果欲打开的⽂件为终端机设备时,则不会将该终端机当成进程控制终端机。
O_TRUNC 若⽂件存在并且以可写的⽅式打开时,此旗标会令⽂件长度清为0,⽽原来存于该⽂件的资料也会消失。
O_APPEND 当读写⽂件时会从⽂件尾开始移动,也就是所写⼊的数据会以附加的⽅式加⼊到⽂件后⾯。
O_NONBLOCK 以不可阻断的⽅式打开⽂件,也就是⽆论有⽆数据读取或等待,都会⽴即返回进程之中。
O_NDELAY 同O_NONBLOCK。
O_SYNC 以同步的⽅式打开⽂件。
O_NOFOLLOW 如果参数pathname 所指的⽂件为⼀符号连接,则会令打开⽂件失败。
C语言读写文件两种方式ASCII和二进制。

C语⾔读写⽂件两种⽅式ASCII和⼆进制。
C语⾔读写⽂件有两种⽅式ASCII 和⼆进制。
现在举例说明:内存中的字符或字符串都是⽂本模式(unicode编码存储,统⼀长度,定长码,⽅便)内存中的其他变量都是以⼆进制编码存储程序的代码以⽂本模式ASCII码⽅式存储1.在windows系统中,⽂本模式下,⽂件以" \r\n"代表换⾏。
若以⽂本模式打开⽂件,并⽤fputs等函数写⼊换⾏符"\n"时,函数会⾃动在"\n"前⾯加上"\r"。
即实际写⼊⽂件的是"\r\n" 。
读取的时候⾃动去掉\r,将\n写⼊内存。
2.在类Unix/Linux系统中⽂本模式下,⽂件以"\n"代表换⾏。
所以Linux系统中在⽂本模式和⼆进制模式下并⽆区别。
注:Windows 采⽤ \r\n 是有原因的,Windows 采⽤了传统的英⽂打字机的模式。
想想看英⽂打字机是如何换⾏的呢?英⽂打字机是选择将⼩车退回⾄起点,这个过程称为回车(carriage return, CR),随后把⼩车调⾄下⼀⾏的位置,这个过程称为换⾏(line feed, LF),这样就完成了英⽂打字机中换⾏过程。
回车(CR)在计算机中使⽤ ASCII 为 13 的字符来表⽰,换⾏(LF)使⽤ASCII 为 10 的字符来表⽰。
这也就是 Windows 的换⾏采⽤ \r\n 来表⽰的原因。
3.当⽤⼆进制模式读取windows下代表换⾏的\r\n时,不能⾃动去掉\r,这样读⼊内存的数据多了⼀个‘\r’,造成跟在‘\r’后⾯的⼆进制位右移,读取必然会出错!4.如果在⽂件中读到0x1B,⽂本模式会认为这是⽂件结束符,也就是⼆进制模型不会对⽂件进⾏处理,⽽⽂本⽅式会按⼀定的⽅式对数据作相应的转换。
5.⽤⽂本模式写⽂件,读取时也要⽤⽂本模式,⼆进制模式同样。
归纳整理Linux下C语言常用的库函数----文件操作

归纳整理Linux下C语⾔常⽤的库函数----⽂件操作在没有IDE的时候,记住⼀些常⽤的库函数的函数名、参数、基本⽤法及注意事项是很有必要的。
参照Linux_C_HS.chm的⽬录,我⼤致将常⽤的函数分为⼀下⼏类:1. 内存及字符串控制及操作2. 字符串转换3. 字符测试4. ⽂件操作5. 时间⽇期6. 常⽤数学函数7. ⽂件内容操作8. ⽂件权限控制9. 进程操作10. 线程操作11. Socket操作12. 信号处理13. 数据结构及算法这次主要总结的是上⾯⿊⾊部分,关于⽂件操作的函数。
系统调⽤归类** 函数名⽤法备注**1. int open(const char *pathname, int flags); open and possibly create a file or device flags 必须包含O_RDONLY, O_WRONLY, or O_RDWR中的任何⼀个**2. int open(const char *pathname, int flags, mode_t mode); UP mode只是在flags中包含O_CREAT时才有效**3. int fsync(int fd); synchronize a file's in-core state with storage device 写完数据close前必须先同步,以防意外**4. off_t lseek(int fd, off_t offset, int whence); 定位⽂件位置第三个参数可以为SEEK_SET SEEK_CUR SEEK_END**5. ssize_t read(int fildes, void *buf, size_t nbyte); UP ⽂件位置会随读取的字节数移动**6. ssize_t write(int fildes, const void *buf, size_t nbyte); UP UP**7. int close(int fd); UP UP**8. void *mmap(void *addr, size_t length, int prot, int flags, 内存映射先⽤fstat得到⽂件⼤⼩,然后使⽤该函数将⽂件内容映射到内存中,然后就可以int fd, off_t offset); 直接调⽤字符串函数操作。
linux c中write、read的用法

linux c中write、read的用法摘要:一、引言二、write 函数的用法1.函数原型2.功能描述3.参数说明4.返回值5.应用实例三、read 函数的用法1.函数原型2.功能描述3.参数说明4.返回值5.应用实例四、总结正文:一、引言Linux C 语言中,write 和read 是两个用于文件操作的重要函数。
它们分别用于向文件中写入数据和从文件中读取数据。
本篇文章将详细介绍这两个函数的用法。
二、write 函数的用法1.函数原型write 函数的原型为:`ssize_t write(int fd, const void *buf, size_t count);`2.功能描述write 函数用于向文件描述符为fd 的文件中写入count 个字节的数据,数据存储在buf 指向的内存区域。
3.参数说明- fd:文件描述符,表示要操作的文件。
- buf:指向要写入文件的数据缓冲区,可以是字符数组、字符指针或者字节数组等。
- count:要写入文件的字节数。
4.返回值write 函数返回实际写入的字节数,如果写入失败,则返回-1,并设置相应的错误码。
5.应用实例下面是一个简单的write 函数应用实例,用于将字符串写入文件:```c#include <stdio.h>#include <string.h>int main() {char str[] = "Hello, world!";int fd = open("output.txt", O_CREAT | O_WRONLY, 0644);if (fd < 0) {perror("open");return 1;}ssize_t ret = write(fd, str, sizeof(str));if (ret == -1) {perror("write");close(fd);return 1;}close(fd);return 0;}```三、read 函数的用法1.函数原型read 函数的原型为:`ssize_t read(int fd, void *buf, size_t count);`2.功能描述read 函数用于从文件描述符为fd 的文件中读取count 个字节的数据,数据存储在buf 指向的内存区域。
linux c 快速写大文件的策略

linux c 快速写大文件的策略Linux C 是一种在 Linux 环境下使用 C 语言进行编程的技术。
在某些应用场景中,我们需要快速地生成大文件。
本文将介绍一些在Linux C 中快速写大文件的策略,以帮助读者更好地理解和应用这些技术。
在Linux C 中,我们可以使用文件操作函数来进行文件的读写操作。
为了快速地写入大文件,我们可以采用以下策略:1. 使用缓冲区:在写入大文件时,直接使用文件操作函数进行写入是非常低效的。
为了提高写入速度,我们可以使用缓冲区来暂存待写入的数据。
通过一次性写入一块较大的数据,可以减少写入操作的次数,从而提高写入速度。
可以使用`fwrite` 函数来一次性写入缓冲区中的数据。
2. 调整文件指针:在写入大文件时,我们可以通过调整文件指针的位置来实现快速写入。
通过调用`fseek` 函数,我们可以将文件指针移动到指定的位置,然后进行写入操作。
这样可以避免每次写入都从文件的开头进行操作,提高写入速度。
3. 并行写入:在某些情况下,我们可以将大文件分割成多个小文件,并使用多个线程或进程同时进行写入操作。
通过并行写入,可以充分利用系统资源,提高写入速度。
在Linux C 中,可以使用线程库或进程库来实现并行写入操作。
4. 优化IO 操作:在进行文件写入操作时,可以采用一些优化技巧来提高写入速度。
例如,可以使用非阻塞 IO 或异步 IO 来实现并行写入操作。
另外,可以设置文件描述符的属性,如缓冲区大小、文件锁等,来优化写入操作。
5. 写入数据压缩:在写入大文件时,可以考虑对待写入的数据进行压缩操作。
通过压缩数据,可以减少写入的数据量,从而提高写入速度。
在 Linux C 中,可以使用压缩库来实现数据压缩操作。
通过以上几种策略的组合应用,我们可以在Linux C 中快速地写入大文件。
在实际应用中,我们可以根据具体的需求和场景选择合适的策略。
同时,我们还需要注意以下几点:1. 写入速度与硬件性能有关:在进行大文件写入时,除了优化写入的策略,硬件性能也是一个重要因素。
Linux_C_C++串口读写串口读写

Linux C/C++串口读写串口简介串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。
常用的串口是RS-232-C 接口(又称EIA RS-232-C)它是在1970 年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。
它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个25 个脚的DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。
传输距离在码元畸变小于4% 的情况下,传输电缆长度应为50 英尺。
Linux 操作系统从一开始就对串行口提供了很好的支持串口操作打开串口在Linux 下串口文件是位于/dev 下的串口一为/dev/ttyS0串口二为/dev/ttyS1设置串口最基本的设置串口包括波特率设置,效验位和停止位设置。
设置这个结构体很复杂,我这里就只说说常见的一些设置:波特率设置设置波特率的例子函数:/***@brief 设置串口通信速率*@param fd 类型 int 打开串口的文件句柄*@param speed 类型 int 串口速度*@return void*/int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,B38400, B19200, B9600, B4800, B2400,B1200, B300, };int name_arr[] ={38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300, }; void set_speed(int fd, int speed){int i;int status;struct termios Opt;tcgetattr(fd, &Opt);for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {if (speed == name_arr[i]) {/*** tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读)或输出缓存(用户程序已经写,但尚未发送)。
Linux文件系统之文件的读写

Linux文件系统之文件的读写展开全文------------------------------------------本文系本站原创,欢迎转载!转载请注明出处:/------------------------------------------一:前言文件的读写是文件系统中最核心也是最复杂的一部份,它牵涉到了很多的概念.之前分析文件系统其它操作的时候,遇到与文件系统相关的读写部份都忽略过去了.在这一节里,来讨论一下文件的读写是怎样实现的.二:I/O请求的概述如之前所提到的,为了提高文件的操作效率,文件系统中的内容都是缓存在内存里的.每当发起一个Rear/Write请求的时候,都会到页面高速缓存中寻找具体的页面.如果页面不存在,则在页面高速缓存中建立相关页面的缓存.如果当前的页面不是最新的.那就必须要到具体的文件系统中读取数据了.一般来说,内核提供了这样的界面:它产生一个I/O请求.这个界面为上层隐藏了下层的不同实现.在这个界面中,将产生的I/O 请求提交给I/O调度.再与I/O调度调用具体的块设备驱动程序.整个过程如下图所示:上图中的Generic Block Layer就是上面描述中所说的I/O的界面.接下来我们以上图从下到上的层次进行讨论.三:块设备驱动块设备与字符设备的区别在于:块设备可以随机的访问,例如磁盘.正是因为它可以随机访问,内核才需要一个高效的手段去管理每一个块设备.例如对磁盘的操作,每次移动磁针都需要花不少的时候,所以尽量让其处理完相同磁道内的请求再将磁针移动到另外的磁道.而对于字符设备来说,不存在这样的顾虑,只需按顺序从里面读/写就可以了.先来看一下块设备驱动所涉及到的数据结构.3.1: block_device结构:struct block_device {//主次驱备号dev_t bd_dev; /* not a kdev_t - it's a search key */ //指向bdev文件系统中块设备对应的文件索引号struct inode * bd_inode; /* will die *///计数器,统计块驱备被打开了多少次int bd_openers;// 块设备打开和关闭的信号量struct semaphore bd_sem; /* open/close mutex *///禁止在块设备上建行新安装的信号量struct semaphore bd_mount_sem; /* mount mutex *///已打开的块设备文件inode链表struct list_head bd_inodes;//块设备描述符的当前拥有者void * bd_holder;//统计字段,统计对bd_holder进行更改的次数int bd_holders;//如果当前块设备是一个分区,此成员指向它所属的磁盘的设备//否则指向该描述符的本身struct block_device * bd_contains;//块大小unsigned bd_block_size;//指向分区描述符的指针struct hd_struct * bd_part;/* number of times partitions within this device have been opened. *///统计字段,统计块设备分区被打开的次数unsigned bd_part_count;//读取块设备分区表时设置的标志int bd_invalidated;//指向块设备所属磁盘的gendiskstruct gendisk * bd_disk;//指向块设备描述符链表的指针struct list_head bd_list;//指向块设备的专门描述符backing_dev_infostruct backing_dev_info *bd_inode_backing_dev_info;/** Private data. You must have bd_claim'ed the block_device* to use this. NOTE: bd_claim allows an owner to claim* the same device multiple times, the owner must take special* care to not mess up bd_private for that case.*///块设备的私有区unsigned long bd_private;}通常,对于块设备来说还涉及到一个分区问题.分区在内核中是用hd_struct来表示的.3.2: hd_struct结构:struct hd_struct {//磁盘分区的起始扇区sector_t start_sect;//分区的长度,即扇区的数目sector_t nr_sects;//内嵌的kobjectstruct kobject kobj;//分区的读操作次数,读取扇区数,写操作次数,写扇区数unsigned reads, read_sectors, writes, write_sectors;//policy:如果分区是只读的,置为1.否则为0//partno:磁盘中分区的相对索引int policy, partno;}每个具体的块设备都会都应一个磁盘,在内核中磁盘用gendisk表示.3.3: gendisk结构:struct gendisk {//磁盘的主驱备号int major; /* major number of driver *///与磁盘关联的第一个设备号int first_minor;//与磁盘关联的设备号范围int minors; /* maximum number of minors, =1 for* disks that can't be partitioned. *///磁盘的名字char disk_name[32]; /* name of major driver *///磁盘的分区描述符数组struct hd_struct **part; /* [indexed by minor] *///块设备的操作指针struct block_device_operations *fops;//指向磁盘请求队列指针struct request_queue *queue;//块设备的私有区void *private_data;//磁盘内存区大小(扇区数目)sector_t capacity;//描述磁盘类型的标志int flags;//devfs 文件系统中的名字char devfs_name[64]; /* devfs crap *///不再使用int number; /* more of the same *///指向磁盘中硬件设备的device指针struct device *driverfs_dev;//内嵌kobject指针struct kobject kobj;//记录磁盘中断定时器struct timer_rand_state *random;//如果只读,此值为1.否则为0int policy;//写入磁盘的扇区数计数器atomic_t sync_io; /* RAID *///统计磁盘队列使用情况的时间戳unsigned long stamp, stamp_idle;//正在进行的I/O操作数int in_flight;//统计每个CPU使用磁盘的情况#ifdef CONFIG_SMPstruct disk_stats *dkstats;#elsestruct disk_stats dkstats;#endif}以上三个数据结构的关系,如下图所示:如上图所示:每个块设备分区的bd_contains会指它的总块设备节点,它的bd_part会指向它的分区表.bd_disk会指向它所属的磁盘.从上图中也可以看出:每个磁盘都会对应一个request_queue.对于上层的I/O请求就是通过它来完成的了.它的结构如下:3.4:request_queue结构:struct request_queue{/** Together with queue_head for cacheline sharing*///待处理请求的链表struct list_head queue_head;//指向队列中首先可能合并的请求描述符struct request *last_merge;//指向I/O调度算法指针elevator_t elevator;/** the queue request freelist, one for reads and one for writes *///为分配请请求描述符所使用的数据结构struct request_list rq;//驱动程序策略例程入口点的方法request_fn_proc *request_fn;//检查是否可能将bio合并到请求队列的最后一个请求的方法merge_request_fn *back_merge_fn;//检查是否可能将bio合并到请求队列的第一个请求中的方法merge_request_fn *front_merge_fn;//试图合并两个相邻请求的方法merge_requests_fn *merge_requests_fn;//将一个新请求插入请求队列时所调用的方法make_request_fn *make_request_fn;//该方法反这个处理请求的命令发送给硬件设备prep_rq_fn *prep_rq_fn;//去掉块设备方法unplug_fn *unplug_fn;//当增加一个新段时,该方法驼回可插入到某个已存在的bio 结构中的字节数merge_bvec_fn *merge_bvec_fn;//将某个请求加入到请求队列时,会调用此方法activity_fn *activity_fn;//刷新请求队列时所调用的方法issue_flush_fn *issue_flush_fn;/** Auto-unplugging state*///插入设备时所用到的定时器struct timer_list unplug_timer;//如果请求队列中待处理请求数大于该值,将立即去掉请求设备int unplug_thresh; /* After this many requests *///去掉设备之间的延迟unsigned long unplug_delay; /* After this many jiffies */ //去掉设备时使用的操作队列struct work_struct unplug_work;//struct backing_dev_info backing_dev_info;/** The queue owner gets to use this for whatever they like.* ll_rw_blk doesn't touch it.*///指向块设备驱动程序中的私有数据void *queuedata;//activity_fn()所用的参数void *activity_data;/** queue needs bounce pages for pages above this limit *///如果页框号大于该值,将使用回弹缓存冲unsigned long bounce_pfn;//回弹缓存区页面的分配标志int bounce_gfp;/** various queue flags, see QUEUE_* below*///描述请求队列的标志unsigned long queue_flags;/** protects queue structures from reentrancy*///指向请求队列锁的指针spinlock_t *queue_lock;/** queue kobject*///内嵌的kobjectstruct kobject kobj;/** queue settings*///请求队列中允许的最大请求数unsigned long nr_requests; /* Max # of requests */ //如果待请求的数目超过了该值,则认为该队列是拥挤的unsigned int nr_congestion_on;//如果待请求数目在这个阀值下,则认为该队列是不拥挤的unsigned int nr_congestion_off;//单个请求所能处理的最大扇区(可调的)unsigned short max_sectors;//单个请求所能处理的最大扇区(硬约束)unsigned short max_hw_sectors;//单个请求所能处理的最大物理段数unsigned short max_phys_segments;//单个请求所能处理的最大物理段数(DMA的约束) unsigned short max_hw_segments;//扇区中以字节为单位的大小unsigned short hardsect_size;//物理段的最大长度(以字节为单位)unsigned int max_segment_size;//段合并的内存边界屏弊字unsigned long seg_boundary_mask;//DMA缓冲区的起始地址和长度的对齐unsigned int dma_alignment;//空闲/忙标记的位图.用于带标记的请求struct blk_queue_tag *queue_tags;//请求队列的引用计数atomic_t refcnt;//请求队列中待处理的请求数unsigned int in_flight;/** sg stuff*///用户定义的命令超时unsigned int sg_timeout;//Not Useunsigned int sg_reserved_size;}request_queue表示的是一个请求队列,每一个请求都是用request来表示的.3.5: request结构:struct request {//用来形成链表struct list_head queuelist; /* looking for ->queue? you must _not_* access it directly, use* blkdev_dequeue_request! *///请求描述符的标志unsigned long flags; /* see REQ_ bits below *//* Maintain bio traversal state for part by part I/O submission.* hard_* are block layer internals, no driver should touch them!*///要传送的下一个扇区sector_t sector; /* next sector to submit *///要传送的扇区数目unsigned long nr_sectors; /* no. of sectors left to submit *//* no. of sectors left to submit in the current segment *///当前bio段传送扇区的数目unsigned int current_nr_sectors;//要传送的下一个扇区号sector_t hard_sector; /* next sector to complete *///整个过程中要传送的扇区号unsigned long hard_nr_sectors; /* no. of sectors left to complete *//* no. of sectors left to complete in the current segment */ //当前bio段要传送的扇区数目unsigned int hard_cur_sectors;/* no. of segments left to submit in the current bio *///unsigned short nr_cbio_segments;/* no. of sectors left to submit in the current bio */unsigned long nr_cbio_sectors;struct bio *cbio; /* next bio to submit *///请求中第一个没有完成的biostruct bio *bio; /* next unfinished bio to complete *///最后的biostruct bio *biotail;//指向I/O调度的私有区void *elevator_private;//请求的状态int rq_status; /* should split this into a few status bits */ //请求所引用的磁盘描述符struct gendisk *rq_disk;//统计传送失败的计数int errors;//请求开始的时间unsigned long start_time;/* Number of scatter-gather DMA addr+len pairs after* physical address coalescing is performed.*///请求的物理段数unsigned short nr_phys_segments;/* Number of scatter-gather addr+len pairs after* physical and DMA remapping hardware coalescing is performed.* This is the number of scatter-gather entries the driver* will actually have to deal with after DMA mapping is done.*///请求的硬段数unsigned short nr_hw_segments;//与请求相关的标识int tag;//数据传送的缓冲区,如果是高端内存,此成员值为NULLchar *buffer;//请求的引用计数int ref_count;//指向包含请求的请求队列描述符request_queue_t *q;struct request_list *rl;//指向数据传送终止的completionstruct completion *waiting;//对设备发达“特殊请求所用到的指针”void *special;/** when request is used as a packet command carrier*///cmd中的数据长度unsigned int cmd_len;//请求类型unsigned char cmd[BLK_MAX_CDB];//data中的数据长度unsigned int data_len;//为了跟踪所传输的数据而使用的指针void *data;//sense字段的数据长度unsigned int sense_len;//指向输出sense缓存区void *sense;//请求超时unsigned int timeout;/** For Power Management requests*///指向电源管理命令所用的结构struct request_pm_state *pm;}请求队列描述符与请求描述符都很复杂,为了简化驱动的设计,内核提供了一个API,供块设备驱动程序来初始化一个请求队列.这就是blk_init_queue().它的代码如下://rfn:驱动程序自动提供的操作I/O的函数.对应请求队列的request_fn//lock:驱动程序提供给请求队列的自旋锁request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock){request_queue_t *q;static int printed;//申请请求队列描述符q = blk_alloc_queue(GFP_KERNEL);if (!q)return NULL;//初始化q->request_listif (blk_init_free_list(q))goto out_init;if (!printed) {printed = 1;printk("Using %s io scheduler\n", chosen_elevator->elevator_name);}//初始化请求队列描述符中的各项操作函数q->request_fn = rfn;q->back_merge_fn = ll_back_merge_fn;q->front_merge_fn = ll_front_merge_fn;q->merge_requests_fn = ll_merge_requests_fn;q->prep_rq_fn = NULL;q->unplug_fn = generic_unplug_device;q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);q->queue_lock = lock;blk_queue_segment_boundary(q, 0xffffffff);//设置q->make_request_fn函数,初始化等待队对列的定时器和等待队列blk_queue_make_request(q, __make_request);//设置max_segment_size,max_hw_segments,max_phys_segments blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);/** all done*///设置等待队列的I/O调度程序if (!elevator_init(q, chosen_elevator))return q;//失败的处理blk_cleanup_queue(q);out_init:kmem_cache_free(requestq_cachep, q);return NULL;}这个函数中初始化了很多操作指针,这个函数在所有块设备中都是一样的,这样就为通用块设备层提供了一个统一的接口.对于块设备驱动的接口就是我们在blk_init_queue中设置的策略例程了.留意一下关于请求队列的各操作的设置,这在后续的分析中会用到.另外,在请求结构中涉及到了bio结构.bio表示一个段.目前内核中关于I/O的所有操作都是由它来表示的.它的结构如下所示:struct bio {//段的起始扇区sector_t bi_sector;//下一个biostruct bio *bi_next; /* request queue link *///段所在的块设备struct block_device *bi_bdev;//bio的标志unsigned long bi_flags; /* status, command, etc *///Read/Writeunsigned long bi_rw; /* bottom bits READ/WRITE,* top bits priority*///bio_vec的项数unsigned short bi_vcnt; /* how many bio_vec's *///当前正在操作的bio_vecunsigned short bi_idx; /* current index into bvl_vec *//* Number of segments in this BIO after* physical address coalescing is performed.*///结合后的片段数目unsigned short bi_phys_segments;/* Number of segments after physical and DMA remapping * hardware coalescing is performed.*///重映射后的片段数目unsigned short bi_hw_segments;//I/O计数unsigned int bi_size; /* residual I/O count *//** To keep track of the max hw size, we account for the* sizes of the first and last virtually mergeable segments* in this bio*///第一个可以合并的段大小unsigned int bi_hw_front_size;//最后一个可以合并的段大小unsigned int bi_hw_back_size;//最大的bio_vec项数unsigned int bi_max_vecs; /* max bvl_vecs we can hold *///bi_io_vec数组struct bio_vec *bi_io_vec; /* the actual vec list *///I/O完成的方法bio_end_io_t *bi_end_io;//使用计数atomic_t bi_cnt; /* pin count *///拥有者的私有区void *bi_private;//销毁此bio的方法bio_destructor_t *bi_destructor; /* destructor */}bio_vec的结构如下:struct bio_vec {//bi_vec所表示的页面struct page *bv_page;//数据区的长度unsigned int bv_len;//在页面中的偏移量unsigned int bv_offset;}关于bio与bio_vec的关系,用下图表示:现在,我们来思考一个问题:当一个I/O请求提交给请求队列后,它是怎么去调用块设备驱动的策略例程去完成这次I/O的呢?还有,当一个I/O请求被提交给请求队列时,会不会立即调用驱动中的策略例程去完成这次I/O呢?实际上,为了提高效率,所有的I/O都会在一个特定的延时之后才会调用策略例程去完成本次I/O.我们来看一个反面的例子,假设I/O在被提交后马上得到执行.例如.磁盘有磁针在磁盘12.现在有一个磁道1的请求.就会将磁针移动到磁道1.操作完后,又有一个请求过来了,它要操作磁道11.然后又会将磁针移到磁道11.操作完后,又有一个请求过来,要求操作磁道4.此时会将磁针移到磁道4.这个例子中,磁针移动的位置是:12->1->11->4.实际上,磁针的定位是一个很耗时的操作.这样下去,毫无疑问会影响整个系统的效率.我们可以在整个延时内,将所有I/O操作按顺序排列在一起,然后再调用策略例程.于是上例的磁针移动就会变成12->11->4->1.此时磁针只会往一个方向移动.至于怎么样排列请求和选取哪一个请求进行操作,这就是I/O调度的任务了.这部份我们在通用块层再进行分析.内核中有两个操作会完成上面的延时过程.即:激活块设备驱动程序和撤消块设备驱动程序.3.6:块设备驱动程序的激活和撤消激活块设备驱动程序和撤消块设备驱动程序在内核中对应的接口为blk_plug_device()和blk_remove_plug().分别看下它们的操作:void blk_plug_device(request_queue_t *q){WARN_ON(!irqs_disabled());/** don't plug a stopped queue, it must be paired with blk_start_queue()* which will restart the queueing*///如果设置了QUEUE_FLAG_STOPPED.直接退出if (test_bit(QUEUE_FLAG_STOPPED, &q->queue_flags))return;//为请求队列设置QUEUE_FLAG_PLUGGED.if (!test_and_set_bit(QUEUE_FLAG_PLUGGED, &q->queue_flags))//如果之前请求队列的状态不为QUEUE_FLAG_PLUGGED,则设置定时器超时时间mod_timer(&q->unplug_timer, jiffies + q->unplug_delay);}int blk_remove_plug(request_queue_t *q){WARN_ON(!irqs_disabled());//将队列QUEUE_FLAG_PLUGGED状态清除if (!test_and_clear_bit(QUEUE_FLAG_PLUGGED,&q->queue_flags))//如果请求队列之前不为QUEUE_FLAG_PLUGGED标志,直接返回return 0;//如果之前是QUEUE_FLAG_PLUGGED标志,则将定时器删除del_timer(&q->unplug_timer);return 1;}如果请求队列状态为QUEUE_FLAG_PLUGGED,且定时器超时,会有什么样的操作呢?回忆在请求队列初始化函数中,blk_init_queue()会调用blk_queue_make_request().它的代码如下:void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn){…………q->unplug_delay = (3 * HZ) / 1000; /* 3 milliseconds */if (q->unplug_delay == 0)q->unplug_delay = 1;INIT_WORK(&q->unplug_work, blk_unplug_work, q);q->unplug_timer.function = blk_unplug_timeout;q->unplug_timer.data = (unsigned long)q;…………}上面设置了定时器的时间间隔为(3*HZ)/1000.定时器超时的处理函数为blk_unplug_timeout().参数为请求队列本身.blk_unplug_timeout()的代码如下:static void blk_unplug_timeout(unsigned long data){request_queue_t *q = (request_queue_t *)data;kblockd_schedule_work(&q->unplug_work);}从上面的代码看出,定时器超时之后,会唤醒q->unplug_work这个工作对列.在blk_queue_make_request()中,对这个工作队列的初始化为: INIT_WORK(&q->unplug_work, blk_unplug_work, q)即工作队列对应的函数为blk_unplug_work().对应的参数为请求队列本身.代码如下:static void blk_unplug_work(void *data){request_queue_t *q = data;q->unplug_fn(q);}到此,就会调用请求队列的unplug_fn()操作.在blk_init_queue()对这个成员的赋值如下所示:q->unplug_fn = generic_unplug_device;generic_unplug_device()对应的代码如下:void __generic_unplug_device(request_queue_t *q){//如果请求队列是QUEUE_FLAG_STOPPED 状态,返回if (test_bit(QUEUE_FLAG_STOPPED, &q->queue_flags))return;//如果请求队列的状态是QUEUE_FLAG_PLUGGED.就会返回1if (!blk_remove_plug(q))return;/** was plugged, fire request_fn if queue has stuff to do*///如果请求对列中的请求,则调用请求队列的reauest_fn函数.也就是驱动程序的//策略例程if (elv_next_request(q))q->request_fn(q);}blk_remove_plug()在上面已经分析过了.这里不再赘述.归根到底,最后的I/O完成操作都会调用块设备驱动的策略例程来完成.四:I/O调度层I/O调度对应的结构如下所示:struct elevator_s{//当要插入一个bio时会调用elevator_merge_fn *elevator_merge_fn;elevator_merged_fn *elevator_merged_fn;elevator_merge_req_fn *elevator_merge_req_fn;//取得下一个请求elevator_next_req_fn *elevator_next_req_fn;//往请求队列中增加请求elevator_add_req_fn *elevator_add_req_fn;elevator_remove_req_fn *elevator_remove_req_fn;elevator_requeue_req_fn *elevator_requeue_req_fn;elevator_queue_empty_fn *elevator_queue_empty_fn;elevator_completed_req_fn *elevator_completed_req_fn;elevator_request_list_fn *elevator_former_req_fn;elevator_request_list_fn *elevator_latter_req_fn;elevator_set_req_fn *elevator_set_req_fn;elevator_put_req_fn *elevator_put_req_fn;elevator_may_queue_fn *elevator_may_queue_fn;//初始化与退出操作elevator_init_fn *elevator_init_fn;elevator_exit_fn *elevator_exit_fn;void *elevator_data;struct kobject kobj;struct kobj_type *elevator_ktype;//调度算法的名字const char *elevator_name;}我们以最简单的NOOP算法为例进行分析.NOOP算法只是做简单的请求合并的操作.的定义如下:elevator_t elevator_noop = {.elevator_merge_fn = elevator_noop_merge,.elevator_merge_req_fn = elevator_noop_merge_requests, .elevator_next_req_fn = elevator_noop_next_request,.elevator_add_req_fn = elevator_noop_add_request,.elevator_name = "noop",}挨个分析里面的各项操作:elevator_noop_merge():在请求队列中寻找能否有可以合并的请求.代码如下:int elevator_noop_merge(request_queue_t *q, struct request **req,struct bio *bio){struct list_head *entry = &q->queue_head;struct request *__rq;int ret;//如果请求队列中有last_merge项.则判断last_merge项是否能够合并//在NOOP中一般都不会设置last_mergeif ((ret = elv_try_last_merge(q, bio))) {*req = q->last_merge;return ret;}//遍历请求队列中的请求while ((entry = entry->prev) != &q->queue_head) {__rq = list_entry_rq(entry);if (__rq->flags & (REQ_SOFTBARRIER | REQ_HARDBARRIER)) break;else if (__rq->flags & REQ_STARTED)break;//如果不是一个fs类型的请求?if (!blk_fs_request(__rq))continue;//判断能否与这个请求合并if ((ret = elv_try_merge(__rq, bio))) {*req = __rq;q->last_merge = __rq;return ret;}}return ELEVATOR_NO_MERGE;}Elv_try_merge()用来判断能否与请求合并,它的代码如下:inline int elv_try_merge(struct request *__rq, struct bio *bio) {int ret = ELEVATOR_NO_MERGE;/** we can merge and sequence is ok, check if it's possible *///判断rq与bio是否为同类型的请求if (elv_rq_merge_ok(__rq, bio)) {//如果请求描述符中的起始扇区+ 扇区数= bio的起始扇区//则将bio加到_rq的后面.//返回ELEVATOR_BACK_MERGEif (__rq->sector + __rq->nr_sectors == bio->bi_sector)ret = ELEVATOR_BACK_MERGE;//如果请求描述符中的起始扇区- 扇区数=bio的起始扇区//则将bio加到_rq的前面//返回ELEVATOR_FRONT_MERGEelse if (__rq->sector - bio_sectors(bio) == bio->bi_sector) ret = ELEVATOR_FRONT_MERGE;//如果不可以合并,返回ELEVATOR_NO_MERGE (值为0)return ret;}elv_rq_merge_ok()代码如下:inline int elv_rq_merge_ok(struct request *rq, struct bio *bio) {//判断rq是否可用if (!rq_mergeable(rq))return 0;/** different data direction or already started, don't merge*///操作是否相同if (bio_data_dir(bio) != rq_data_dir(rq))return 0;/** same device and no special stuff set, merge is ok*///要操作的对象是否一样if (rq->rq_disk == bio->bi_bdev->bd_disk &&!rq->waiting && !rq->special)return 1;return 0;}注意:如果检查成功返回1.失败返回0.elevator_noop_merge_requests():将next 从请求队列中取出.代码如下:void elevator_noop_merge_requests(request_queue_t *q, struct request *req,struct request *next){list_del_init(&next->queuelist);}从上面的代码中看到,NOOP算法从请求队列中取出请求,只需要取链表结点即可.不需要进行额外的操作.elevator_noop_next_request():取得下一个请求.代码如下:struct request *elevator_noop_next_request(request_queue_t *q){if (!list_empty(&q->queue_head))return list_entry_rq(q->queue_head.next);return NULL;}很简单,取链表的下一个结点.elevator_noop_add_request():往请求队列中插入一个请求.代码如下:void elevator_noop_add_request(request_queue_t *q, struct request *rq,int where){//默认是将rq插和到循环链表末尾struct list_head *insert = q->queue_head.prev;//如果要插到请求队列的前面if (where == ELEVATOR_INSERT_FRONT)insert = &q->queue_head;//不管是什么样的操作,都将新的请求插入到请求队列的末尾list_add_tail(&rq->queuelist, &q->queue_head);/** new merges must not precede this barrier*/if (rq->flags & REQ_HARDBARRIER)q->last_merge = NULL;else if (!q->last_merge)q->last_merge = rq;}五:通用块层的处理通用块层的入口点为generic_make_request().它的代码如下:void generic_make_request(struct bio *bio){request_queue_t *q;sector_t maxsector;//nr_sectors:要操作的扇区数int ret, nr_sectors = bio_sectors(bio);//可能会引起睡眠might_sleep();/* Test device or partition size, when known. *///最大扇区数目maxsector = bio->bi_bdev->bd_inode->i_size >> 9;if (maxsector) {//bio操作的起始扇区sector_t sector = bio->bi_sector;//如果最大扇区数<要操作的扇区数or 最大扇区数与起始扇区的差值小于要操作的扇区数//非法的情况if (maxsector < nr_sectors ||maxsector - nr_sectors < sector) {char b[BDEVNAME_SIZE];/* This may well happen - the kernel calls* bread() without checking the size of the* device, e.g., when mounting a device. */printk(KERN_INFO"attempt to access beyond end of device\n");printk(KERN_INFO "%s: rw=%ld, want=%Lu, limit=%Lu\n", bdevname(bio->bi_bdev, b),bio->bi_rw,(unsigned long long) sector + nr_sectors,(long long) maxsector);set_bit(BIO_EOF, &bio->bi_flags);goto end_io;}}/** Resolve the mapping until finished. (drivers are* still free to implement/resolve their own stacking* by explicitly returning 0)** NOTE: we don't repeat the blk_size check for each new device.* Stacking drivers are expected to know what they are doing.*/do {char b[BDEVNAME_SIZE];//取得块设备的请求对列q = bdev_get_queue(bio->bi_bdev);if (!q) {//请求队列不存在printk(KERN_ERR"generic_make_request: Trying to access ""nonexistent block-device %s (%Lu)\n",bdevname(bio->bi_bdev, b),(long long) bio->bi_sector);end_io://最终会调用bio->bi_end_iobio_endio(bio, bio->bi_size, -EIO);break;}//非法的情况if (unlikely(bio_sectors(bio) > q->max_hw_sectors)) {printk("bio too big device %s (%u > %u)\n",bdevname(bio->bi_bdev, b),bio_sectors(bio),q->max_hw_sectors);goto end_io;}//如果请求队列为QUEUE_FLAG_DEAD//退出if (test_bit(QUEUE_FLAG_DEAD, &q->queue_flags))goto end_io;/** If this device has partitions, remap block n* of partition p to block n+start(p) of the disk.*///如果当前块设备是一个分区,则转到分区所属的块设备blk_partition_remap(bio);//调用请求队列的make_request_fn()ret = q->make_request_fn(q, bio);} while (ret);}在blk_init_queue()中对请求队列的make_request_fn的设置如下所示:blk_init_queue()—> blk_queue_make_request(q, __make_request)void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn){…………q->make_request_fn = mfn;……}这里,等待队对的make_request_fn就被设置为了__make_request.这个函数的代码如下:static int __make_request(request_queue_t *q, struct bio *bio) {struct request *req, *freereq = NULL;int el_ret, rw, nr_sectors, cur_nr_sectors, barrier, err;sector_t sector;//bio的起始扇区sector = bio->bi_sector;//扇区数目nr_sectors = bio_sectors(bio);//当前bio中的bio_vec的扇区数目cur_nr_sectors = bio_cur_sectors(bio);//读/写rw = bio_data_dir(bio);/** low level driver can indicate that it wants pages above a* certain limit bounced to low memory (ie for highmem, or even* ISA dma in theory)*///建立一个弹性回环缓存blk_queue_bounce(q, &bio);spin_lock_prefetch(q->queue_lock);barrier = bio_barrier(bio);if (barrier && !(q->queue_flags & (1 <<QUEUE_FLAG_ORDERED))) {err = -EOPNOTSUPP;goto end_io;}again:spin_lock_irq(q->queue_lock);//请求队列是空的if (elv_queue_empty(q)) {//激活块设备驱动blk_plug_device(q);goto get_rq;}if (barrier)goto get_rq;//调用I/O调度的elevator_merge_fn方法,判断这个bio能否和其它请求合并//如果可以合并,req参数将返回与之合并的请求描述符el_ret = elv_merge(q, &req, bio);switch (el_ret) {//可以合并.且bio加到req的后面case ELEVATOR_BACK_MERGE:BUG_ON(!rq_mergeable(req));if (!q->back_merge_fn(q, req, bio))break;req->biotail->bi_next = bio;req->biotail = bio;req->nr_sectors = req->hard_nr_sectors += nr_sectors; drive_stat_acct(req, nr_sectors, 0);if (!attempt_back_merge(q, req))elv_merged_request(q, req);goto out;//可以合并.且bio加到req的前面case ELEVATOR_FRONT_MERGE:BUG_ON(!rq_mergeable(req));if (!q->front_merge_fn(q, req, bio))break;bio->bi_next = req->bio;req->cbio = req->bio = bio;req->nr_cbio_segments = bio_segments(bio);req->nr_cbio_sectors = bio_sectors(bio);/** may not be valid. if the low level driver said* it didn't need a bounce buffer then it better* not touch req->buffer either...*/req->buffer = bio_data(bio);req->current_nr_sectors = cur_nr_sectors;req->hard_cur_sectors = cur_nr_sectors;req->sector = req->hard_sector = sector;req->nr_sectors = req->hard_nr_sectors += nr_sectors; drive_stat_acct(req, nr_sectors, 0);if (!attempt_front_merge(q, req))elv_merged_request(q, req);goto out;/** elevator says don't/can't merge. get new request*///不可以合并.申请一个新的请求,将且加入请求队列case ELEVATOR_NO_MERGE:break;default:printk("elevator returned crap (%d)\n", el_ret);BUG();}/** Grab a free request from the freelist - if that is empty, check * if we are doing read ahead and abort instead of blocking for* a free slot.*/get_rq://freereq:是新分配的请求描述符if (freereq) {req = freereq;freereq = NULL;} else {//分配一个请求描述符spin_unlock_irq(q->queue_lock);if ((freereq = get_request(q, rw, GFP_ATOMIC)) == NULL) { /** READA bit set*///分配失败err = -EWOULDBLOCK;if (bio_rw_ahead(bio))goto end_io;freereq = get_request_wait(q, rw);}goto again;}req->flags |= REQ_CMD;/** inherit FAILFAST from bio (for read-ahead, and explicit FAILFAST)*/if (bio_rw_ahead(bio) || bio_failfast(bio))req->flags |= REQ_FAILFAST;/** REQ_BARRIER implies no merging, but lets make it explicit */if (barrier)req->flags |= (REQ_HARDBARRIER | REQ_NOMERGE);//初始化新分配的请求描述符req->errors = 0;req->hard_sector = req->sector = sector;req->hard_nr_sectors = req->nr_sectors = nr_sectors;req->current_nr_sectors = req->hard_cur_sectors = cur_nr_sectors;req->nr_phys_segments = bio_phys_segments(q, bio);req->nr_hw_segments = bio_hw_segments(q, bio);req->nr_cbio_segments = bio_segments(bio);req->nr_cbio_sectors = bio_sectors(bio);req->buffer = bio_data(bio); /* see ->buffer comment above */req->waiting = NULL;//将bio 关联到请求描述符req->cbio = req->bio = req->biotail = bio;req->rq_disk = bio->bi_bdev->bd_disk;req->start_time = jiffies;//请将求描述符添加到请求队列中add_request(q, req);out: (R)if (freereq)__blk_put_request(q, freereq);//如果定义了BIO_RW_SYNC.//将调用__generic_unplug_device将块设备驱动,它会直接调用驱动程序的策略例程if (bio_sync(bio))__generic_unplug_device(q);spin_unlock_irq(q->queue_lock);return 0;end_io:bio_endio(bio, nr_sectors << 9, err);return 0;}这个函数的逻辑比较简单,它判断bio能否与请求队列中存在的请求合并,如果可以合并,将其它合并到现有的请求.如果不能合并,则新建一个请求描述符,然后把它插入到请求队列中.上面的代码可以结合之前分析的NOOP算法进行理解.重点分析一下请求描述符的分配过程:分配一个请求描述符的过程如下所示:if ((freereq = get_request(q, rw, GFP_ATOMIC)) == NULL) { /** READA bit set*///分配失败err = -EWOULDBLOCK;if (bio_rw_ahead(bio))goto end_io;freereq = get_request_wait(q, rw);}在分析这段代码之前,先来讨论一下关于请求描述符的分配方式.记得我们在分析请求队列描述符的时候,request_queue中有一个成员:struct request_list rq;它的数据结构如下:struct request_list {//读/写请求描述符的分配计数int count[2];//分配缓存池mempool_t *rq_pool;//如果没有空闲内存时.读/写请求的等待队列wait_queue_head_t wait[2];。
Linux(CC++)下的文件操作open、fopen与freopen

Linux(CC++)下的⽂件操作open、fopen与freopenopen是下的底层系统调⽤函数,fopen与freopen c/c++下的标准I/O库函数,带输⼊/输出缓冲。
linxu下的fopen是open的封装函数,fopen最终还是要调⽤底层的系统调⽤open。
所以在linux下如果需要对设备进⾏明确的控制,那最好使⽤底层系统调⽤(open),open对应的⽂件操作有:close, read, write,ioctl 等。
fopen 对应的⽂件操作有:fclose, fread, fwrite, freopen, fseek, ftell, rewind等。
freopen⽤于重定向输⼊输出流的函数,该函数可以在不改变代码原貌的情况下改变输⼊输出环境,但使⽤时应当保证流是可靠的。
详细见第3部分。
-------------------------------------------------------------------------------------------------------------------open和fopen的区别:1,fread是带缓冲的,read不带缓冲.2,fopen是标准c⾥定义的,open是POSIX中定义的.3,fread可以读⼀个结构.read在linux/unix中读⼆进制与普通⽂件没有区别.4,fopen不能指定要创建⽂件的权限.open可以指定权限.5,fopen返回⽂件指针,open返回⽂件描述符(整数).6,linux/unix中任何设备都是⽂件,都可以⽤open,read.-------------------------------------------------------------------------------------------------------------------1、open系统调⽤(linux)需要包含头⽂件:#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>函数原型:int open( const char * pathname, int oflags);int open( const char * pathname,int oflags, mode_t mode);mode仅当创建新⽂件时才使⽤,⽤于指定⽂件的访问权限。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux下C语言的文件(fputc,fgetc,fwrite,fread对文件读写操
作)
//==================================
fputc 向文件写入字符
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("test.txt","w"))==NULL)
{
printf("不能打开文件\n");
exit(0);
}
while ((ch=getchar())!='\n')
fputc( ch, fp );
fclose(fp);
}
-------------
小提示:
fp=fopen("test.txt","w") ,把"w"改为"a" 可以创建文件并且追加写入内容
exit(0); 需要包含stdlib.h 头文件,才能使用
//============================================================
fgetc 读取字符
#include <stdio.h>
#include <stdlib.h>
main( int argc, char *argv[] )
{
char ch;
FILE *fp;
int i;
if((fp=fopen(argv[1],"r"))==NULL)
{
printf("不能打开文件\n");
exit(0);
}
while ((ch=fgetc(fp))!=EOF)
putchar(ch);
fclose(fp);
}
文件结尾,通过判断EOF
//============================================================== fwrite 的使用
使数组或结构体等类型可以进行一次性读写
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp1;
int i;
struct student{
char name[10];
int age;
float score[2];
char addr[15];
}stu;
if((fp1=fopen("test.txt","wb"))==NULL)
{
printf("不能打开文件");
exit(0);
}
printf("请输入信息,姓名年龄分数1 分数2 地址:\n");
for( i=0;i<2;i++)
{
scanf("%s %d %f %f %s",,&stu.age,&stu.score[0],&stu.score[1], stu.addr);
fwrite(&stu,sizeof(stu),1,fp1);
}
fclose(fp1);
}
//=============================================================== fread 的使用
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp1;
int i;
struct student{
char name[10];
int age;
float score[2];
char addr[15];
}stu;
if((fp1=fopen("test.txt","rb"))==NULL)
{
printf("不能打开文件");
exit(0);
}
printf("读取文件的内容如下:\n");
for (i=0;i<2;i++)
{
fread(&stu,sizeof(stu),1,fp1);
printf("%s %d %7.2f %7.2f %s\n",,stu.age,stu.score[0],stu.score[1],stu.addr);
}
fclose(fp1);
}。