【IT专家】Linux下二进制方式读写文件
关于二进制文件读写的详细解说
先介绍函数,我们一共要用到三个函数,fopen,fread,fwrite。
二进制读写的顺序是用fopen以二进制方式打开读写文件,然后使用fread和fwrite两个函数将数据写入二进制文件中。
下面我们看看一个拷贝程序的源码:2中,注意fread的返回值,这个值需要在fwrite的时候将会用到。
后面是关于fopen,fread,fwrite三个函数的详细说明。
fopen(打开文件)相关函数open,fclose表头文件#include<stdio.h>定义函数FILE * fopen(const char * path,const char * mode);函数说明参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。
mode有下列几种形态字符串:r 打开只读文件,该文件必须存在。
r+ 打开可读写的文件,该文件必须存在。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。
若文件不存在则建立该文件。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。
若文件不存在则建立该文件。
a 以附加的方式打开只写文件。
若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
a+ 以附加方式打开可读写的文件。
若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
上述的形态字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b 字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。
不过在POSIX系统,包含Linux都会忽略该字符。
由fopen()所建立的新文件会具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)权限,此文件权限也会参考umask值。
返回值文件顺利打开后,指向该流的文件指针就会被返回。
Linux环境下实现对文件读写操作
Linux环境下实现对⽂件读写操作---- 今天分享⼀下在linux系统在实现对⽂件读写⼀些基本的操作,在这之前我们要掌握⼀些基本的技能在Linux环境下。
⽐如查看命令和⼀个函数的具体⽤法,就是相当于查⼿册,在Linux下有⼀个man⼿册⾮常有⽤:man查询⼿册man 1 +命令这⾥的1表⽰为查询的是Linux命令man 2 xxx 这⾥的2表⽰为查询的是linux apiman 3 xxx 这⾥的3表⽰为查询的是c库函数在了解了这个后我们就可以开始来实现标题说的操作了。
⼀、在linux环境下常⽤⽂件接⼝函数:open、close、write、read、lseek。
⼆、⽂件操作的基本步骤分为:a、在linux系统中要操作⼀个⽂件,⼀般是先open打开⼀个⽂件,得到⼀个⽂件扫描描述符,然后对⽂件进⾏读写操作(或其他操作),最后关闭⽂件即可。
b、对⽂件进⾏操作时,⼀定要先打开⽂件,然后再进⾏对⽂件操作(打开⽂件不成功的话,就操作不了),最后操作⽂件完毕后,⼀定要关闭⽂件,否则可能会造成⽂件损坏c、⽂件平时是存放在块设备中的⽂件系统中的,我们把这个⽂件叫做静态⽂件,当我们去打开⼀个⽂件时,linux内核做的操作包括:内核在进程中建⽴了⼀个打开⽂件的数据结构,记录下我们打开的这个⽂件,内核在内存中申请⼀段内存,并且将静态⽂件的内容从块设备中读取到内存中特定地址管理存放(叫动态⽂件)d、打开⽂件后,以后对这个⽂件的读写操作,都是针对内存中这⼀份动态⽂件的,⽽不是针对静态⽂件的。
当我们对动态⽂件进⾏读写后,此时内存中的动态⽂件和块设备中的静态⽂件就不同步了,当我们close 关闭动态⽂件时,close内部内核将内存中的动态⽂件的内容去更新(同步)块设备中的静态⽂件。
三、为什么是这样操作?以块设备本⾝有读写限制(回忆Nandflash、SD、等块设备的读写特征),本⾝对块设备进⾏操作⾮常不灵活。
⽽内存可以按字节为单位来操作。
二进制文件读写
今天终于弄明白怎样使用C++读写二进制文件了。
要读取文件必须包含<fstream>头文件,这里包含了C++读写文件的方法。
可以使用fstream类,这个类可以对文件进行读写操作。
1、打开文件。
打开文件可以有两种方式,第一种可以使用fstream类的构造函数。
fstream file("test.dat",ios_base::in|ios_base::out|ios_base::app); 另外一种方法就是使用open函数。
fstream file;file.open("test.dat",ios_base::in|ios_base::out|ios_base::app);这样就可以打开一个可读写的文件了。
如果文件不存在的话,就会创建一个新文件并且以读写方式打开。
这里需要说明一点,如果文件不存在的话,open函数中第二个参数必须包含ios_base::out|ios_base::app,否则就不能正确创建文件。
2、写文件。
先进性写文件的操作否则读一个空文件是没有意义的。
既然是写二进制文件可以向文件中写入一个整形值。
写二进制字符只能使用write函数。
但是write函数的原形是write(const char * ch, int size)。
第一个参数是char *类型,所以需要把将要写入文件的int类型转换成char *类型。
这里的转换困扰了我好几天,不过终于弄明白了。
代码如下。
int temp;file.write((char *)(&temp),sizeof(temp));3、读文件。
可以写文件了,读文件就好办多了。
读文件需要用到read函数。
其参数和write大致相同,read(const char * ch, int size)。
要把内容读到int类型变量中同样涉及到一个类型转换的问题。
和写文件一样。
int readInt;file.read((char *)(&readInt),sizeof(readInt));这样文件中的int值就读入到int型变量readInt中了。
二进制文件的读写以及相关心得
二进制文件读写分析有关TXT文件以及bin文件处理的测试代码int main(){//fstreamfile("D:\\test.dat",ios_base::in|ios_base::out|ios_base::app);fstream file;file.open("D:\\test1.dat",ios_base::out);{int temp[10] ={0x11,0x13,0x14,0x1F,0x1D,0x11,0x11,0x12,0x11,0x11};int temp1[10];file.write((char *)temp,sizeof(temp));file.close();file.open("D:\\test1.dat",ios_base::in);file.read((char *)temp,sizeof(temp));unsigned int i;cout<<sizeof(temp)<<endl;for(i = 0;i < sizeof(temp)/sizeof(unsigned int);i++){temp1[i] = temp[i];cout<<temp[i]<<endl;}file.close();file.open("D:\\test1.dat",ios_base::out);temp1[6] = 0x16;file.write((char *)temp1,sizeof(temp));file.close();}file.open("D:\\test1.dat",ios_base::out|ios_base::app);unsigned char temp[10] = {'a','b','a','a','a','d','a','C','a','a'}; test *test1 = (test *)malloc(sizeof(test));test1->projectId = 'U';test1->password = 0x12;for(int i = 0;i < 10;i ++){test1->someThings[i] = temp[i];}test1->sm = (unsigned long)0x41;test1->fuck = 'M';file.write((char *)test1,sizeof(test));file.close();test *test2 = (test *)malloc(sizeof(test));file.open("D:\\test1.dat",ios_base::in);file.seekg(0x28);file.read((char *)test2,sizeof(test));for(int i = 0;i < 10;i ++){cout<<test2->someThings[i]<<endl;}file.close();return 0;}有关bin文件处理的测试笔记<1> 我一直以为自己很熟悉如何使用C/C++中的二进制文件,可今天测试的时候突然发现程序生成的二进制文件和文本文件一样。
linuxc编程:文件的读写
linuxc编程:⽂件的读写Linux系统中提供了系统调⽤函数open()和close()⽤于打开和关闭⼀个存在的⽂件int open(const char *pathname,int flags)int open(const char *pathname,int flags,mode_t mode)int open(const char *pathname,mode_t mode)其中flag代表⽂件的打开⽅式O_RDONLY: 以只读⽅式打开⽂件O_WRONLY:以只写的⽅式打开⽂件O_RDWR:以读写的⽅式打开⽂件O_CREAT:若打开的⽂件不存在,则创建该⽂件O_EXCL:如果打开⽂件是设置了O_CREAT,但是该⽂件存在,则导致调⽤失败O_TRUNC:如果以只写或只读⽅式打开⼀个已存在的⽂件,将该⽂件截⾄0O_APPEND:追加的⽅式打开⽂件O_NONBLOCK:⽤于⾮堵塞接⼝i/oO_NODELAYO_SYNC:当数据被写⼊外存或者其他设备后,操作才返回。
其中mode是打开的权限S_IRWXU 00700 设置⽂件所有者的读,写,执⾏权限S_IRWXG 00070 设置⽂件所在⽤户组的读,写,执⾏权限S_IRWXO 00007 设置其他⽤户的读,写,执⾏权限S_IRUSR 00400 设置⽂件所有者的读权限S_IWUSR 00200 设置⽂件所有的写权限S_IXUSR 00100 设置⽂件所有者的执⾏权限S_IRGRP 00040 设置⽤户组的读权限S_IWGRP 00020 设置⽤户组的写权限S_IXGRP 00010 设置⽤户组的执⾏权限S_IROTH 00004 设置其他⽤户的读权限S_IWOTH 00002 设置其他⽤户的写权限S_IXOTH 00001 设置其他⽤户的执⾏权限⽂件的读操作:ssize_t read(int fd,void *buf,size_t count);fd代表⽂件描述符,buf代表读取的数据存放的buf指针所指向的缓冲区,count代表读取数据的字节数函数调⽤成功,返回为读取的字节数,否则返回-1⽂件读和写的例⼦void open_and_read_file(){int fd,n;char buf[100];char *path="/home/zhf/zhf/c_prj/c_test.txt";fd=open(path,O_RDWR);n=read(fd,buf,20);printf("the content is %s\n",buf);}在这⾥⽂件都是从头开始读的,那么如果我想从某个位置⽐如第N个字节开始读取的时候该如何操作呢,这⾥就需要⽤到⽂件定位函数lseek off_t lseek(int fildes,off_t offset,int whence);\fildes是⽂件描述符offset是偏移量whence代表⽤于偏移时的相对位置,可以取如下的⼏个值SEEK_SET: 从⽂件的开头位置计算偏移量SEEK_CUR: 从当前的位置开始计算偏移量SEEK_END: 从⽂件的结尾开始计算偏移量函数修改如下:⾸先采⽤lseek函数将⽂件定位到从第11个字节开始,然后read将从第11个字节开始读取。
同一个文件在windows和linux下计算md5哈希不一致的原因及解决方法
同⼀个⽂件在windows和linux下计算md5哈希不⼀致的原
因及解决⽅法
最近项⽬需要,需要对客户传过来的⽂件进⾏MD5校验,在实现的过程中前前后后遇到了若⼲问题,在这⾥总结⼀下。
md5的计算采⽤openssl实现,具体代码⽹上很多,这⾥不再赘述。
需要注意的问题
1 读取⽂件内容时,⽂件打开⽅式要⽤⼆进制⽅式(rb),因为⽤户⽂件有可能是linux格式,如果⽤⽂本⽅式打开,可能会改变原始的内容,造成计算不准。
2 结果检验。
windows可以随便下载⼀个md5计算⼯具,⽹上很多,我⽤的是HashMyFiles。
linux下⾯,md5sum ⽂件名即可。
还有⼀个隐藏得问题需要注意,我们在这⾥好⼀阵郁闷。
程序编写完毕,再windows测试都通过了,把⽂件上传到linux,再运⾏程序,居然算出来的md5哈希和windows不⼀样。
经过⼀阵跟踪、断点、打印发现,⽂件上传到linux后,⼤⼩居然发⽣了变化,原来问题出在ftp,ftp上传得过程中采⽤了⽂本模式,会把⽂件中换⾏回车替换为换⾏。
于是重新⽤⼆进制模式上传,计算结果⼀致,问题解决。
总结⼀下:⽂件打开读取要⽤⼆进制⽅式,⽂件传输也要⽤⼆进制⽅式。
以上这篇同⼀个⽂件在windows和linux下计算md5哈希不⼀致的原因及解决⽅法就是⼩编分享给⼤家的全部内容了,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
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];。
二进制文件读写(写给新手)
二进制文件读写(写给新手)二进制文件读写(写给新手)FortranFortran 二进制文件读写【给新手】一).一般问题二进制文件与我们通常使用的文本文件储存方式有根本的不同。
这样的不同很难用言语表达,自己亲自看一看,理解起来会容易得多。
因此,我推荐学习二进制文件读写的朋友安装一款十六进制编辑器。
这样的编辑器有很多,在我们的 CVF 附带的集成开发环境下就可以(将二进制文件拖动到 IDE 窗口后松开)。
Visual Studio 2005 也是可以的。
(不过需要在 File 菜单下 Open,File)另外推荐一款使用较多的软件,叫做 UltraEdit(以下简称 UE)。
是很不错的文本编辑器,也能做十六进制编辑器使用。
为什么要用十六进制编辑器?而不用 2 进制呢?因为 2 进制实在太小,书写起来会很长,很不直观。
而我们的计算机把 8 位作为一个字节。
刚好 2 ** 8 = 256 = 16 ** 2。
用 8 位 2 进制表达的数,我们用 2 个十六进制数据来表达,更直观和方便。
二).文件格式所有文件,笼统意义上将可以区分为两类,一类是文本文件,一类是二进制文件。
1).文本文件文本文件用记事本等文本编辑器打开,我们可以看懂上面的信息。
所以使用比较广泛。
通常一个文本文件分为很多很多行,作为数据储存时,还有列的概念。
实际上,储存在硬盘或其他介质上,文件内容是线一样储存的,列是用空格或 Tab 间隔,行是用回车和换行符间隔。
以 ANSI 编码(使用较多)的文本文件来说,例如我们储存如下信息:101112需要的空间是:3 行×每行 2 个字符 + 2 个回车符 + 2 个换行符 = 10 字节。
文本文件储存数据是有格式,无数据类型的。
比如 10 这个数据,并不指定是整型还是实型还是字符串。
它有长度,就是 2,两个字节。
储存时计算机储存它的 ASCII 码:31h,30h。
(十六进制表示)。
回车符是:0Dh,换行符:0Ah。
二进制方式读取文本文件的方法-定义说明解析
二进制方式读取文本文件的方法-概述说明以及解释1.引言1.1 概述二进制方式读取文本文件是一种在计算机科学中常见的操作方法。
它通过以0和1的二进制形式来表示数据,与传统的基于字符的文本文件读取方式不同。
本文将讨论二进制方式读取文本文件的基本概念、方法和应用,以及总结其优势和局限性,展望未来发展方向。
通过深入了解二进制方式读取文件的相关知识,读者将能够更好地理解这一重要的文件操作方式,并且能够更加灵活和高效地进行文件读取和处理。
1.2 文章结构文章结构部分:本文将分为三个主要部分:引言、正文和结论。
在引言部分中,将简要介绍本文要讨论的主题,并说明文章的结构和目的。
在正文部分中,将分为三个部分来讨论二进制读取文件的基本概念、方法和应用。
在结论部分中,将总结二进制读取文件的优势,并讨论其局限性和未来发展的展望。
通过这样的结构,读者能够清晰地了解本文的内容并对二进制读取文件有更深入的理解。
的内容1.3 目的本文的主要目的是探讨二进制方式读取文本文件的方法。
通过深入分析二进制读取文件的基本概念、方法和应用,旨在帮助读者更深入地了解和掌握二进制文件读取的技术和优势。
同时,也将探讨二进制方式读取文件的局限性,并展望其未来的发展方向。
通过本文的阐述,读者将能够更全面地认识和理解二进制文件读取的重要性、应用价值以及可能的发展趋势,为软件开发和数据处理提供更多的思路和参考。
2.正文2.1 二进制读取文件的基本概念二进制读取文件是指以二进制的形式来解析和读取计算机文件的内容。
在计算机中,一切数据都是以二进制形式存储的,包括文本文件。
因此,通过二进制方式读取文件可以更加直接和高效地获取文件中的数据。
在二进制读取文件的过程中,文件中的数据被以连续的字节序列方式读取,并且可以以不同的方式进行解析和处理。
与文本方式读取文件不同,二进制方式读取文件直接操作文件的底层数据,可以更加自由地处理文件中的各种数据类型,包括整数、浮点数、字符串等。
linux操作系统的文件类型
linux操作系统的文件类型Linux操作系统的文件类型一、普通文件普通文件是Linux操作系统中最常见的文件类型之一,它包含了大量的文本、代码、二进制数据等信息。
这些文件可以通过文本编辑器或特定的应用程序进行打开和编辑。
普通文件可以分为以下几种类型:1. 文本文件:文本文件是由字符组成的文件,可以通过文本编辑器打开并查看其中的内容。
常见的文本文件包括配置文件、日志文件、源代码文件等。
2. 二进制文件:二进制文件是由二进制数据组成的文件,无法直接通过文本编辑器查看其内容。
二进制文件通常包含了可执行代码、图像、音频、视频等非文本数据。
3. 数据文件:数据文件是存储着特定数据的文件,可以通过特定的应用程序进行读取和处理。
常见的数据文件包括数据库文件、电子表格文件、图像文件等。
二、目录文件目录文件是用于组织和管理其他文件的文件类型,它包含了其他文件和目录的信息。
目录文件可以通过文件浏览器或命令行界面进行访问和操作。
目录文件可以分为以下几种类型:1. 根目录:根目录是Linux操作系统中的最顶层目录,即所有其他目录和文件的父目录。
在Unix/Linux系统中,根目录通常表示为"/"。
2. 用户目录:用户目录是每个用户在系统中的个人目录,用于存储用户的个人文件和配置信息。
用户目录的路径通常为"/home/用户名"。
3. 系统目录:系统目录是用于存储系统文件和应用程序的目录,包括了各种系统配置文件、库文件、可执行文件等。
常见的系统目录包括"/etc"、"/bin"、"/usr"等。
三、特殊文件特殊文件是Linux操作系统中的一类特殊文件类型,它们不同于普通文件和目录文件,具有特殊的用途和功能。
特殊文件包括以下几种类型:1. 设备文件:设备文件是用于访问系统硬件设备的文件,包括了字符设备文件和块设备文件。
字符设备文件用于访问字符设备,如键盘、鼠标等;块设备文件用于访问块设备,如硬盘、闪存等。
linux 二进制比较命令
linux 二进制比较命令Linux二进制比较命令是一种用于比较两个二进制文件之间差异的工具。
在Linux系统中,二进制比较命令可以帮助我们了解文件内容的变化,检测文件的完整性,并进行版本控制和安全验证。
本文将介绍常用的Linux二进制比较命令,并说明它们的用途和使用方法。
1. cmp命令:cmp命令用于比较两个文件的内容,并输出第一个不同的字节的位置。
它逐字节地比较两个文件,并在遇到不同字节时停止。
如果两个文件完全相同,则不会输出任何内容。
cmp命令的基本用法如下:```cmp file1 file2```其中,file1和file2是要比较的两个文件的名称。
如果文件内容相同,则不会输出任何内容;如果不同,则会输出第一个不同字节的位置。
2. diff命令:diff命令用于比较两个文件的内容,并输出两个文件之间的差异。
它逐行比较两个文件,并标记出不同的行。
diff命令的基本用法如下:```diff file1 file2```其中,file1和file2是要比较的两个文件的名称。
diff命令会逐行比较两个文件,并输出不同的行。
输出中以"<"表示file1中的行,以">"表示file2中的行。
3. colordiff命令:colordiff命令是diff命令的一个扩展工具,它可以将diff命令输出的结果以彩色显示。
相比于diff命令,colordiff命令在阅读差异内容时更加直观。
colordiff命令的基本用法如下:```colordiff file1 file2```其中,file1和file2是要比较的两个文件的名称。
colordiff命令会将diff命令输出的结果以彩色显示,使差异更加明显。
4. bsdiff和bspatch命令:bsdiff和bspatch是一对用于生成和应用二进制补丁的命令。
bsdiff命令用于比较两个文件的差异,并生成一个二进制补丁文件;bspatch命令用于根据二进制补丁文件和原始文件,生成一个新的文件。
文件头的二进制读取操作
文件头的二进制读取操作1.引言1.1 概述概述部分的内容可以如下所述:概述:二进制文件是计算机中一种常见的文件存储格式,广泛应用于各种领域,如软件开发、数据存储和文件传输等。
与文本文件不同,二进制文件以二进制编码形式存储数据,包含了计算机能够直接读取和理解的机器指令。
而文件头是二进制文件中的特定部分,用于标识文件的类型和格式,同时还包含了一些重要的元数据信息。
文章结构:本文将着重讨论二进制文件头的读取操作。
首先,通过对二进制文件头的定义和作用的介绍,我们将了解到文件头在二进制文件中的重要性。
然后,我们将深入探讨二进制读取操作的基本原理,揭示其背后的工作机制。
最后,我们将总结二进制读取操作的重要性,并提供一些提高二进制读取效率的方法。
目的:本文的目的在于帮助读者理解二进制文件头的读取操作,掌握其基本原理,并认识到其在实际应用中的重要性。
通过深入了解和学习二进制读取操作,读者将能够更好地应用这一技术,提高对二进制文件的处理能力,并在实际项目开发和数据处理中取得更好的效果。
未来的发展中,我们可以通过研究和探索更多的二进制读取操作技术,改进现有的读取方法,并提出新的读取策略,以适应不断变化的需求和挑战。
二进制读取操作作为计算机领域中的重要技术之一,其进一步的研究和应用将有助于推动计算机技术的发展,并为我们带来更多的创新和突破。
1.2文章结构文章结构是一篇长文的骨架,它有助于读者更好地理解并组织文章的内容。
在本篇文章中,我们的主题是文件头的二进制读取操作,为了让读者更加清晰地了解文章的结构,本文将按照以下方式展示。
首先,在引言部分,我们将提供一个概述,介绍本文的主题和背景。
然后,我们将说明文章的结构,以及每个部分的目的和重要性。
在正文部分,我们将重点介绍二进制文件头的定义和作用。
我们将解释什么是二进制文件头,以及它在文件中的位置和作用。
此外,我们还将探讨如何进行二进制读取操作,包括基本原理和流程。
我们将介绍如何通过读取文件头来获取有关文件的信息,并且深入解析二进制读取的相关概念和技巧。
Linux命令高级技巧使用objdump和readelf查看可执行文件信息
Linux命令高级技巧使用objdump和readelf查看可执行文件信息在Linux系统中,objdump和readelf是两个常用的命令,用于查看可执行文件(二进制文件)的详细信息。
通过使用这两个命令,我们可以深入了解可执行文件的结构、函数、符号表等相关信息,有助于我们进行程序分析和调试。
本文将介绍如何使用objdump和readelf命令来查看可执行文件的高级技巧。
一、使用objdump查看可执行文件信息objdump命令是GNU Binutils工具集中的一个重要组成部分,它可以用于反汇编可执行文件,显示可执行文件的各个节(section)的内容。
下面是一些常用的objdump命令选项:1. objdump -h <可执行文件名>:显示可执行文件的节表信息。
该命令会列出可执行文件中各个节的起始偏移地址、大小等信息。
2. objdump -S <可执行文件名>:显示可执行文件的源代码和汇编代码。
该命令会将可执行文件中的机器码和源代码进行关联,并以汇编代码的形式显示出来,便于分析。
3. objdump -t <可执行文件名>:显示可执行文件的符号表。
符号表中包含了可执行文件中定义和引用的函数、变量等符号信息。
除了上述常用选项外,objdump还提供了很多其他有用的选项,可以根据实际需求进行选择。
二、使用readelf查看可执行文件信息readelf是GNU Binutils工具集中的另一个重要工具,它可以用于查看和分析可执行文件的各个节的信息,以及可执行文件的头部信息。
下面是一些常用的readelf命令选项:1. readelf -h <可执行文件名>:显示可执行文件的头部信息。
头部信息包含了可执行文件的类型、入口地址、节表偏移等重要信息。
2. readelf -S <可执行文件名>:显示可执行文件的节表信息。
节表信息包含了可执行文件中各个节的起始地址、大小、访问属性等详细信息。
Linux内核下读写文件
Linux内核下读写⽂件2020-03-27关键字:在 Linux 内核开发,通常是嵌⼊式领域的内核开发过程中,难免会有需要访问⽂件系统中的⽂件的需求。
但 Linux 内核中可没有像在⽤户态那样有⽂件IO和标准IO可以直接对⽂件进⾏ open()/fopen() , read()/fread() , write()/fwrite() , close()/fclose() 操作。
不过所幸,在 ./kernel/include/linux/fs.h 中提供了有相对应的函数供我们对⽂件系统中的普通⽂件进⾏IO操作。
这些函数为:1、filp_open()2、filp_close()3、vfs_read()4、vfs_write()可以将这套函数理解成是在内核态的“⽂件IO”接⼝。
1、filp_open()函数函数原型如下:struct file *filp_open(const char *, int, umode_t);参数1是要打开的⽂件的路径。
直接填⽂件系统中的路径就⾏了,最好填绝对路径。
参数2是⽂件的读写模式。
常⽤的值有 O_RDONLY , O_WDONLY , O_RDWR , O_CREAT。
这个参数的值与⽂件IO中的⼀样,它们被定义在./kernel/include/uapi/asm-generic/fcntl.h 中。
参数3则是⽂件的权限了,即 0666 , 0755 形式的⼋进制数值。
如果是只读模式,直接填 0 即可。
返回值是指向所打开⽂件的结构体指针。
这个结构体被定义在 ./kernel/include/linux/fs.h 中。
2、filp_close()函数函数原型如下:int filp_close(struct file *, fl_owner_t id);参数1就是filp_open()函数的返回值。
参数2⼀般填0即可。
返回值表⽰这个⽂件的关闭结果,值0表⽰成功关闭。
3、vfs_read()函数函数原型如下:ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *);参数1是filp_open()函数的返回值。
linux 二进制比较命令
linux 二进制比较命令Linux二进制比较命令在Linux系统中,二进制比较命令是一种用于对比两个二进制文件的工具。
它可以帮助我们快速准确地判断两个文件是否相同,以及找出差异之处。
本文将介绍常用的二进制比较命令,并详细说明其使用方法和注意事项。
1. cmp命令cmp命令是Linux系统中最基本的二进制比较命令之一。
它可以逐字节地比较两个文件,并在找到不同字节时输出其位置和差异值。
使用cmp命令的基本语法如下:cmp [选项] 文件1 文件2其中,选项可以是以下之一:- b:以字节为单位进行比较(默认为块)。
- l:以长整型为单位进行比较。
- s:不输出任何错误信息。
例如,要比较文件file1和file2:cmp file1 file2cmp命令将输出不同字节的位置和差异值,如果两个文件完全相同,则不会输出任何信息。
2. diff命令diff命令是另一个常用的二进制比较命令,它可以逐行比较两个文件的内容,并输出不同之处。
使用diff命令的基本语法如下:diff [选项] 文件1 文件2其中,选项可以是以下之一:- b:忽略空白字符的差异。
- c:输出上下文格式的差异结果。
- q:仅显示有无差异,不显示具体内容。
例如,要比较文件file1和file2:diff file1 file2diff命令将输出不同行的具体内容,以及差异所在的行号。
3. od命令od命令是Linux系统中用于显示文件内容的命令,它可以将文件内容以不同的进制形式显示出来。
在二进制比较中,od命令可以将文件内容转换为十六进制形式,以便于观察和比较。
使用od命令的基本语法如下:od [选项] 文件其中,选项可以是以下之一:- x:以十六进制形式显示文件内容。
- c:以字符形式显示文件内容。
例如,要将文件file以十六进制形式显示出来:od -x fileod命令将输出文件内容的十六进制表示,并且每个字节都有对应的位置信息。
4. md5sum命令md5sum命令是Linux系统中用于计算文件MD5值的命令,它可以将文件内容转换为一个唯一的128位哈希值。
Linux系统下如何查看及修改文件读写权限
Linux系统下如何查看及修改文件读写权限查看文件权限的语句:在终端输入:ls -l xxx.xxx (xxx.xxx是文件名)那么就会出现相类似的信息,主要都是这些:-rw-rw-r--一共有10位数其中:最前面那个 - 代表的是类型中间那三个 rw- 代表的是所有者(user)然后那三个 rw- 代表的是组群(group)最后那三个 r-- 代表的是其他人(other)然后我再解释一下后面那9位数:r 表示文件可以被读(read)w 表示文件可以被写(write)x 表示文件可以被执行(如果它是程序的话)- 表示相应的权限还没有被授予现在该说说修改文件权限了在终端输入:chmod o w xxx.xxx表示给其他人授予写xxx.xxx这个文件的权限chmod go-rw xxx.xxx表示删除xxx.xxx中组群和其他人的读和写的权限其中:u 代表所有者(user)g 代表所有者所在的组群(group)o 代表其他人,但不是u和g (other)a 代表全部的人,也就是包括u,g和or 表示文件可以被读(read)w 表示文件可以被写(write)x 表示文件可以被执行(如果它是程序的话)其中:rwx也可以用数字来代替r ------------4w -----------2x ------------1- ------------0行动:表示添加权限- 表示删除权限= 表示使之成为唯一的权限当大家都明白了上面的东西之后,那么我们常见的以下的一些权限就很容易都明白了:-rw------- (600) 只有所有者才有读和写的权限-rw-r--r-- (644) 只有所有者才有读和写的权限,组群和其他人只有读的权限-rwx------ (700) 只有所有者才有读,写,执行的权限-rwxr-xr-x (755) 只有所有者才有读,写,执行的权限,组群和其他人只有读和执行的权限-rwx--x--x (711) 只有所有者才有读,写,执行的权限,组群和其他人只有执行的权限-rw-rw-rw- (666) 每个人都有读写的权限-rwxrwxrwx (777) 每个人都有读写和执行的权限Linux文件和目录访问权限设置(二)2009年02月16日星期一下午 12:54五、使用chmod和数字改变文件或目录的访问权限文件和目录的权限表示,是用rwx这三个字符来代表所有者、用户组和其他用户的权限。
linux读取文件参数
linux读取文件参数
Linux系统中读取文件时可以使用多种参数来实现不同的功能和需求。
以下是一些常见的读取文件参数:
1. `-r` 或 `--read`,以只读模式打开文件,允许读取文件内容但不允许修改。
2. `-w` 或 `--write`,以可写模式打开文件,允许读取和修改文件内容。
3. `-a` 或 `--append`,以追加模式打开文件,在文件末尾添加新内容而不覆盖原有内容。
4. `-f` 或 `--force`,强制打开文件,即使文件已被其他程序锁定或只读。
5. `-n` 或 `--line-number`,显示文件内容时显示行号,方便定位和查找。
6. `-m` 或 `--more`,逐页显示文件内容,按空格键翻页。
7. `-p` 或 `--paginate`,分页显示文件内容,类似于more 命令,但支持更多功能。
8. `-s` 或 `--silent`,静默模式,不显示任何提示信息或错误信息。
9. `-t` 或 `--tail`,实时监控文件内容变化,并输出新增内容,常用于查看日志文件。
10. `-h` 或 `--help`,显示帮助信息,列出可用的参数和示例。
这些参数可以根据具体的需求和情况进行组合和调整,以实现对文件的灵活读取操作。
同时,还可以结合管道符号、重定向符号等操作符号,实现更复杂的文件读取和处理操作。
希望以上信息能够满足你的需求,如果还有其他问题,欢迎继续提问。
5.1.3 二进制文件操作
读取二进制文件
file.bmp
bmp格式
头部信息 (共54字节)
文件信息头 (共14字节)
位图信息头 (共40字节)
剩余像素信息
存放宽高信息
宽高信息存放在头部 信息的第18-25字节
读取二进制文件
import struct img= open('D:\\file.bmp', 'rb') fileheader = img.read(54) pixel_buffer = img.read() size = fileheader[18:26] img.close() width, height = struct.unpack('ii',size) print(f'图像宽度为{width},图像高度为{height}')
二进制文件操作
பைடு நூலகம்
二进制文件
二进制文件
计算机文件格式。 数据以二进制形式存储。 不存储字符,但包含任意类型的数 据,如图像、音频、视频、可执行 文件、压缩文件等。
文本文件
计算机文件。 数 据 通 常 以 ASCII 码 或 Unicode 字 符编码。 主要由字符(英文字母、数字、符号 等)组成。
读取二进制文件
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
本文由我司收集整编,推荐下载,如有疑问,请与我司联系
Linux下二进制方式读写文件
2017/03/29 0 最近在做项目需要把内存数据写入到文件中,然后再从文件中以二进制方式读出使用。
由于接触Linux开发时间不长,开始询问度娘,度娘的
回答是使用以wb方式打开文件后使用fwrite把数据写入文件,以rb方式打开文件
后使用fread读出数据。
下面详细介绍一下相关的函数极其使用说明。
需要用的头文件为stdio.h,函数结构为fwrite、fread、fseek、ftell、fstat。
1. fwrite size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream); 返回:返回实际写入的数据块数目(1)buffer:是一个指针,对fwrite来说,是要获取数据的地址;(2)size:要写入内容的单字节数;(3)count: 要进行写入size 字节的数据项的个数;(4)stream: 目标文件指针;(5)返回实际写入的数据项个数count。
说明:写入到文件的哪里?这个与文件的打开模式有关,如果是w ,则是从file pointer指向的地址开始写,替换掉之后的内容,文件的长度可以不变,stream的位置移动count个数;如果是a ,则从文件的末尾开始添加,文件长度加大。
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件2. fread size_t fread(void *buffer, size_t size, size_t count, FILE *stream) ; 返回:返回真实写入的项数,若大于count则意味着产生了错误。
另外,产生错误后,文件
位置指示器是无法确定的。
若其他stream或buffer为空指针,或在unicode模式中
写入的字节数为奇数,此函数设置errno为EINVAL以及返回0. (1)buffer:用于接收数据的内存地址;(2)size:要读的每个数据项的字节数,单位是字节;(3)count:要读count个数据项,每个数据项size个字节.;(4)stream: 目标文件指针;3. fseek int fseek(FILE * stream,long offset,int whence); 返回:成功返回0,失败返回-1,errno会存放错误代码。
(1)stream: 目标文件指针;(2)offset: 相对于whence的偏移量;(3)whence:绝对位置。
说明:fseek()用来移动文件流的读写位置。
参数stream为已打开的文件指针,参数offset为根据参数whence来移动读写位置的位移数。
注意1:参数whence为下列其中一种::。