自己动手写操作系统:2.C语言文件操作,制作系统引导程序

来源:互联网 发布:华方 线切割编程 编辑:程序博客网 时间:2024/06/09 22:30
本次学习的目标如下,我们带着目标来学习,这样效果更好。

目标:1.把 A文件中的内容 读出并写入到 B 文件中,且在B文件的511字节和512字节赋值 0x55 0xaa
目标:2.指定A的内容:由汇编程序生成,写入到B中,且在B文件的511字节和512字节赋值 0x55 0xaa

开始学习:
一、c语言文件的操作模式:
r:可读
w:可写
a:在文件后面追加
b:二进制模式
+:可读可写权限

"r" 打开,只读;
"w" 打开,文件指针指到头,只写;
"a" 打开,指向文件尾,在已存在文件中追加;
"rb" 打开一个二进制文件,只读;
"wb" 打开一个二进制文件,只写;
"ab" 打开一个二进制文件,进行追加 ;
"r+" 以读/写方式打开一个已存在的文件;
"w+" 以读/写方式建立一个新的文本文件 ;
"a+" 以读/写方式打开一个文件文件进行追加 ;
"rb+" 以读/写方式打开一个二进制文件;
"wb+" 以读/写方式建立一个新的二进制文件 ;
"ab+" 以读/写方式打开一个二进制文件进行追加

打开文件
FILE *fp;  if((fp=fopen("test","w"))==NULL) {      printf("File cannot be opened/n");      exit();  }  else  {    printf("File opened for writing/n");  }

关闭文件
if(fclose(fp)!=0) {      printf("File cannot be closed/n");       exit(1);   }   else  {    printf("File is now closed/n");   }

它表示该函数将关闭FILE指针对应的文件,并返回一个整数值。
若成功地关闭了文件,则返回一个0值,否则返回一个非0值
 int fcloseall(); //关闭全部打开的文件
 该函数将关闭所有已打开的文件,将各文件缓冲区未装满的内容写到相应的文件中去,接着释放这些缓冲区,并返回关闭文件的数目。
 如关闭了4个文件,则当执行: n=fcloseall(); 时,n应为4。
 
 二、文件的读写
 1.一次只读写文件中的一个字符
int fgetc(FILE *stream);//读完返回-1
int getchar(void);
int fputc(int ch,FILE *stream);
int putchar(int ch);
int getc(FILE *stream);
int putc(int ch,FILE *stream);
注:为什么不适用char ch 而使用 int ch
注意,这里使用char ch,其实是不科学的,因为最后判断结束标志时,
是看ch!=EOF,而EOF的值为-1,这显然和char是不能比较的。
#include "stdio.h"   #include <stdlib.h>  int main() {       FILE *fp;       char ch;      if((fp=fopen("myfile.txt","r"))==NULL) {          printf("file cannot be opened/n");           exit(1);       }       while((ch=fgetc(fp))!=EOF)           fputc(ch,stdout);       fclose(fp);   }   

2、读写文件中字符串的函数
(1) char *fgets(char *string,int n,FILE *stream); 
其中fgets()函数将把由流指针指定的文件中n-1个字符,读到由指针string指向的字符数组中去
fgets()函数读到'/n'就停止,而不管是否达到数目要求。同时在读取字符串的最后加上'/0'。 fgets()函数执行完以后,返回一个指向该串的指针。如果读到文件尾或出错,则均返回一个空指针NULL,
所以常用feof()函数来测定是否到了文件尾或者是用ferror()函数来测试是否出错,
例如下面的程序用fgets()函数读test.txt文件中的第一行并显示出来:
#include "stdio.h"   int main() {      FILE *fp;       char str[128];       if((fp=fopen("test.txt","r"))==NULL) {          printf("cannot open file/n"); exit(1);      }       while(!feof(fp)) {          if(fgets(str,128,fp)!=NULL)          printf("%s",str);      }      fclose(fp);  }   (2) char *gets(char *s); gets()函数执行时,只要未遇到换行符或文件结束标志,将一直读下去。因此读到什么时候为止,需要用户进行控制,否则可能造成存储区的溢出。
(3) int fputs(char *string,FILE *stream);
fputs()函数向指定文件写入一个由string指向的字符串,'/0'不写入文件。
(4) int fprintf(FILE *stream,char *format,variable-list);
(5) int fscanf(FILE *stream,char *format,variable-list);
fprintf()和fscanf()同printf()和scanf()函数类似,不同之处就是printf()函数是想显示器输出,
fprintf()则是向流指针指向的文件输出;fscanf()是从文件输入。 可以用来进行格式转化

三、清除和设置文件缓冲区
 int fflush(FILE *stream); 
int flushall();
 fflush()函数将清除由stream指向的文件缓冲区里的内容,常用于写完一些数据后,立即用该函数清除缓冲区,以免误操作时,破坏原来的数据。
flushall()将清除所有打开文件所对应的文件缓冲区。
(2).设置文件缓冲区函数
 void setbuf(FILE *stream,char *buf); 
void setvbuf(FILE *stream,char *buf,int type,unsigned size); 
这两个函数将使得打开文件后,用户可建立自己的文件缓冲区,而不使用fopen()函数打开文件设定的默认缓冲区。 对于setbuf()函数,buf指出的缓冲区长度由头文件stdio.h中定义的宏BUFSIZE的值决定,缺省值为512字节。
当选定buf为空时,setbuf函数将使的文件I/O不带缓冲。
而对setvbuf函数,则由malloc函数来分配缓冲区。参数size指明了缓冲区的长度(必须大于0),而参数type则表示了缓冲的类型,其值可以取如下值: type 值 含义 _IOFBF 文件全部缓冲,
即缓冲区装满后,才能对文件读写 _IOLBF 文件行缓冲,即缓冲区接收到一个换行符时,才能对文件读写 _IONBF 文件不缓冲,此时忽略buf,size的值,直接读写文件,不再经过文件缓冲区缓冲。

四、文件的随机读写函数
(1).移动文件指针函数:
 long ftell(FILE *stream);
 int rewind(FILE *stream);
 fseek(FILE *stream,long offset,int origin);
 函数ftell()用来得到文件指针离文件开头的偏移量。当返回值是-1时表示出错。 rewind()函数用于文件指针移到文件的开头,当移动成功时,返回0,否则返回一个非0值。 fseek()函数用于把文件指针以origin为起点移动offset个字节,其中origin指出的位置可有以下几种:
 origin 数值 代表的具体位置 
SEEK_SET 0 文件开头
 SEEK_CUR 1 文件指针当前位置
 SEEK_END 2 文件尾 
例如: fseek(fp,10L,0); 把文件指针从文件开头移到第10字节处,由于offset参数要求是长整型数,故其数后带L。
 fseek(fp,-15L,2); 把文件指针从文件尾向前移动15字节。
(2).文件随机读写函数
 int fread(void *ptr,int size,int nitems,FILE *stream);
 int fwrite(void *ptr,int size,int nitems,FILE *stream);
 fread()函数从流指针指定的文件中读取nitems个数据项,每个数据项的长度为size个字节,读取的nitems数据项存入由ptr指针指向的内存缓冲区中,
在执行fread()函数时,文件指针随着读取的字节数而向后移动,最后移动结束的位置等于实际读出的字节数。
该函数执行结束后,将返回实际读出的数据项数,这个数据项数不一定等于设置的nitems,因为若文件中没有足够的数据项,或读中间出错,都会导致返回的数据项数少于设置的nitems。
当返回数不等于nitems时,可以用feof()或ferror()函数进行检查。 fwrite()函数从ptr指向的缓冲区中取出长度为size字节的nitems个数据项,写入到流指针stream指向的文件中,执行该操作后,文件指针将向后移动,移动的字节数等于写入文件的字节数目。
该函数操作完成后,也将返回写入的数据项数。

五、非标准函数的读写
int open(char *filename, int access);
 int read(int handle, void *buf, int count);
 int write(int handle, void *buf, int count);
 int lseek(int handle, long offset, int fromwhere); 
 
一般用于终端设别、管道、网络非标准IO

五、目标实现:
好了,有了上面的基础知识我们开始实现第一个小目标:
目标:1.把 A文件中的内容 读出并写入到 B 文件中,且在B文件的511字节和512字节赋值 0x55 0xaa
#include "stdio.h"#include <stdlib.h>void TestResult(FILE * fp){    if(feof(fp))    {        //printf("Read OK\n");  //读取成功不许要打印消息    }    else if(ferror(fp))    {        printf("Read REEOR\n");    }    }int main(){    char Shan[512] = {0};    FILE *fpA = NULL;    FILE *fpB = NULL;    int  ret = 0;    int  DateNo = 512;        fpA = fopen("A.txt","r");//源文件    fpB = fopen("B.txt","w+");//目的文件        if(0 != fflush(NULL))    {        printf("flushall REEOR\n");        return 0;    }    if(NULL == fpA || NULL == fpB)    {        printf("open file error\n!");        return 0;    }            ret = fread(Shan, 1, DateNo,fpA);//读取A文件数据    if(ret != DateNo)    {        TestResult(fpA);    }        Shan[512-1-1] = 0x55;// 写512最后两字字节    Shan[512-1]   = 0xaa;        ret = fwrite(Shan, 1, DateNo,fpB);//把缓冲文件写入到B文件中    if(ret != DateNo)    {        TestResult(fpB);    }        if(0 != fcloseall())//关闭所有打开的文件    {        printf("fcloseall REEOR\n");        return 0;       }        return 0;}

我们再来实现第二个小目标:
目标:2.指定A的内容:由汇编程序生成,写入到B中,且在B文件的511字节和512字节赋值 0x55 0xaa
其实第一个和第二个目标的区别在于读入的A文件不一样
我们结合之前的操作cpu寄存器的知识,写出A的汇编代码:
org 0x7c00jmp entryentry:    mov ax, 0    mov ss, ax    mov ds, ax    mov es, ax    mov si,msg       putloop:            ;按序执行    mov al, [si]    add si, 1    cmp al, 0    je fin    mov ah, 0x0e    mov bx, 15    int 0x10    jmp putloop    fin:    HLT    jmp finmsg:        "Hello OS!"    DB 0

使用汇编编译工具对A进行汇编:
nasm A.asm -o A.boot

这样生成的 A.boot 文件就是二进制文件,原来读写文件的c程序只要稍微改变一下打开A文件的地方就可以了,修改后程序如下:
#include "stdio.h"#include <stdlib.h>void TestResult(FILE * fp){    if(feof(fp))    {        //printf("Read OK\n");  //读取成功不许要打印消息    }    else if(ferror(fp))    {        printf("Read REEOR\n");    }    }int main(){    char Shan[512] = {0};    FILE *fpA = NULL;    FILE *fpB = NULL;    int  ret = 0;    int  DateNo = 512;        fpA = fopen("A.boot","rb");//1.修改文件名为A.boot 2.b表示打开对象为二进制文件    fpB = fopen("system.img","w+");//目的文件        if(0 != fflush(NULL))    {        printf("flushall REEOR\n");        return 0;    }    if(NULL == fpA || NULL == fpB)    {        printf("open file error\n!");        return 0;    }            ret = fread(Shan, 1, DateNo,fpA);//读取A文件数据    if(ret != DateNo)    {        TestResult(fpA);    }        Shan[512-1-1] = 0x55;// 写512最后两字字节    Shan[512-1]   = 0xaa;        ret = fwrite(Shan, 1, DateNo,fpB);//把缓冲文件写入到B文件中    if(ret != DateNo)    {        TestResult(fpB);    }        if(0 != fcloseall())//关闭所有打开的文件    {        printf("fcloseall REEOR\n");        return 0;       }        return 0;}

经过测试,生成的B文件中的确是A.boot的二进制文件,511和512字节分别为 0x55,0xaa

有了这一节的准备,下一节我们制作真正可以引导的操作系统映像文件将会容易很多。
原创粉丝点击