fopen-fread-fwrite-open-read-write原理源碼詳解

最近在研究kb引擎的時候,看recast源碼和navmesh源碼中的數據加載時候看到了fread和fwrite,發現有兩種不同的寫法,請看下面的圖示。突然間想起來,2種不同的寫法效率是否會不同那,所以就想看看底層系統函數的源碼,於是乎

recast源碼寫法

kb源碼

研究了一下fwrite, write,printf的實現原理, 給大家分享一下,給大家展示一下

一. 代碼片段
#include    <stdio.h>
#include    <unistd.h>
#include    <string.h>
#include    <errno.h>
 
int main(int argc,char**argv)
{
    char buffer[]="hello world";
    
    //使用'printf' 進行打印
    printf("%s",buffer);
     
    //使用'fwrite' 進行打印
    //fwrite(buffer, 1, strlen(buf), stdout);
    fwrite(buffer, strlen(buf), 1, stdout);
    
    //使用'write' function to print char 
    write(STDOUT_FILENO, &buf, strlen(buf));
    
    return(0);
}

執行結果

hello world
hello world
hello world

 二.具體實現原理
實現原理是如何的那?

 在unix下對文件的操作有2組系統函數

第一組:fopen, fread, fwrite
fopen 系列是標準的C庫函數

第二組:open, read, write
open系列是 POSIX 定義的,是UNIX系統裏的system call 文件句柄(file handles) ,也稱文件結構指針, 文件描述符(file descriptors)是一個整形變量, 我們常知道的, stdout(標準輸出), stdin(標準輸入), stderr(標準錯誤),其實都是文件句柄,每一個文件句柄都會對應1個文件描述符,stdout, stdin, stderr,正好對應文件描述0,1,2
fread和fwrite接口

NAME
       fread, fwrite - binary stream input/output
 
SYNOPSIS
       #include <stdio.h>
 
       size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
 
       size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
 

三. 分析實現原理
os是px4使用的nuttx。

3.1 nuttx的配置選項
# CONFIG_STDIO_DISABLE_BUFFERING is not set
CONFIG_STDIO_BUFFER_SIZE=32   #配置文件的緩衝區大小
CONFIG_STDIO_LINEBUFFER=y     #是否打開文件緩衝

3.2 nuttx的文件句柄和文件描述符的定義

3.2.1 文件結構體
文件位置NuttX/nuttx/include/nuttx/fs/fs.h, 文件結構中,包含了描述文件的緩衝區,這個是文件結構的很大的特點

#if CONFIG_NFILE_STREAMS > 0
struct file_struct
{
  int                fs_fd;        //綁定的文件描述符
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
  sem_t              fs_sem;       /* For thread safety */
  pid_t              fs_holder;    /* Holder of sem */
  int                fs_counts;    /* Number of times sem is held */
  FAR unsigned char *fs_bufstart;  //執行緩衝區的頭部,緩衝是f_open動態分配的
  FAR unsigned char *fs_bufend;    /* Pointer to 1 past end of buffer */
  FAR unsigned char *fs_bufpos;    /* Current position in buffer */
  FAR unsigned char *fs_bufread;   /* Pointer to 1 past last buffered read char. */
#endif
  uint16_t           fs_oflags;    /* Open mode flags */
  uint8_t            fs_flags;     /* Stream flags */
#if CONFIG_NUNGET_CHARS > 0
  uint8_t            fs_nungotten; /* The number of characters buffered for ungetc */
  unsigned char      fs_ungotten[CONFIG_NUNGET_CHARS];
#endif
};

3.2.2 文件描述符的定義
linux的每個文件描述符,都綁定了1個靜態的file opreation的文件接口,綁定了f_priv驅動的對象,操作底層的寄存器

struct file
{
  int               f_oflags;   /* Open mode flags */
  off_t             f_pos;      /* File position */
  FAR struct inode *f_inode;    /* Driver or file system interface */
  void             *f_priv;     /* Per file driver private data */
};
 
/* This defines a list of files indexed by the file descriptor */
 
#if CONFIG_NFILE_DESCRIPTORS > 0
struct filelist
{
  sem_t   fl_sem;               /* Manage access to the file list */
  struct file fl_files[CONFIG_NFILE_DESCRIPTORS];
};

3.2.3  標準輸入輸出的流

這個文件句柄,是每個線程創建的時候,自動創建的,自動綁定到文件描述符0,1,2上

#define stdin      (&sched_getstreams()->sl_streams[0])
#define stdout     (&sched_getstreams()->sl_streams[1])
#define stderr     (&sched_getstreams()->sl_streams[2])

3.2.4 posix的標準接口定義

 
 

struct file_operations
{
  /* The device driver open method differs from the mountpoint open method */
 
  int     (*open)(FAR struct file *filep);
 
  /* The following methods must be identical in signature and position because
   * the struct file_operations and struct mountp_operations are treated like
   * unions.
   */
 
  int     (*close)(FAR struct file *filep);
  ssize_t (*read)(FAR struct file *filep, FAR char *buffer, size_t buflen);
  ssize_t (*write)(FAR struct file *filep, FAR const char *buffer, size_t buflen);
  off_t   (*seek)(FAR struct file *filep, off_t offset, int whence);
  int     (*ioctl)(FAR struct file *filep, int cmd, unsigned long arg);
 
  /* The two structures need not be common after this point */
 
#ifndef CONFIG_DISABLE_POLL
  int     (*poll)(FAR struct file *filep, struct pollfd *fds, bool setup);
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  int     (*unlink)(FAR struct inode *inode);
#endif

3.3.1 fopen接口實現過程

FAR FILE *fopen(FAR const char *path, FAR const char *mode)
    //打開文件描述符
    fd = open(path, oflags, 0666);
        
        //把文件句柄綁定到os中
        -> ret = fs_fdopen(fd, oflags, NULL); 
        
        //動態文件緩衝區, 確定start和end的位置, 
        stream->fs_bufstart =    group_malloc(tcb->group, CONFIG_STDIO_BUFFER_SIZE);
        stream->fs_bufend  = &stream->fs_bufstart[CONFIG_STDIO_BUFFER_SIZE];
        stream->fs_bufpos  = stream->fs_bufstart;
        stream->fs_bufread = stream->fs_bufstart;
        
        //綁定文件描述到os的流中
        stream->fs_fd      = fd;  
};

3.3.2 fwrite接口的實現

 

size_t fwrite(FAR const void *ptr, size_t size, size_t n_items, FAR FILE *stream)
    ->  size_t  full_size = n_items * (size_t)size;
      ->  bytes_written = lib_fwrite(ptr, full_size, stream); //寫入全部字節
 
      //先數據寫到文件緩存區中
      for (dest = stream->fs_bufpos; gulp_size > 0; gulp_size--)
        *dest++ = *src++;
      
      //緩衝區滿了,才寫入到寫到真正的設備上
      if (dest >= stream->fs_bufend)
          int bytes_buffered = lib_fflush(stream, false);
            ->   bytes_written = write(stream->fs_fd, src, nbuffer);

3.3.3 fread實現過程

 

size_t fread(FAR void *ptr, size_t size, size_t n_items, FAR FILE *stream)
    bytes_read = lib_fread(ptr, full_size, stream);
        
        //如果緩衝區有數據,直接先從緩衝區拿到數據
        while ((count > 0) && (stream->fs_bufpos < stream->fs_bufread))
            *dest++ = *stream->fs_bufpos++;
            count--;
          }
        
          buffer_available = stream->fs_bufend - stream->fs_bufread;
 
        //如果數據不夠,就直接從文件中讀取
        -> if (count > buffer_available)
            bytes_read = read(stream->fs_fd, dest, count);
 
        //如果需要的數據,小於緩衝區容納的大小,那麼讀取整個緩衝區的大小
        ->   if (count 《 buffer_available)
          bytes_read = read(stream->fs_fd, dest, buffer_available);

3.4 printf接口實現

printf的最底層函數是up_putc() 這個函數,就是smt32阻塞等待發送完成,而且,這個up_putc可以被任何地方調用

int printf(FAR const IPTR char *fmt, ...)
  int vfprintf(FAR FILE *stream, FAR const IPTR char *fmt, va_list ap)
        ->lib_stdoutstream(&stdoutstream, stream);
          ->  outstream->public.put = stdoutstream_putc;
            ->  result = fputc(ch, sthis->stream);
                -> ret = lib_fwrite(&buf, 1, stream); // 下面就和fwrite的實現就一樣了
                  ->  if (dev->isconsole)
                  -> ret = uart_irqwrite(dev, buffer, buflen);
                     ->uart_putc('\r'); //  #define uart_putc(ch) up_putc(ch)     
                          -> void up_lowputc(char ch)
                              -> while ((getreg32(CONSOLE_BASE+A1X_UART_LSR_OFFSET) & UART_LSR_THRE) == 0);
                              putreg32((uint32_t)ch, CONSOLE_BASE+A1X_UART_THR_OFFSET);
        -> n = lib_vsprintf(&stdoutstream.public, fmt, ap);
              ->   obj->put(obj, FMT_CHAR);
          if (FMT_CHAR == '\n')
              (void)obj->flush(obj); //刷新

3. 5文件系統掛載

文件系統的掛載是我一直疑惑的問題,這此終於明白了文件系統的掛載的問題,塊設備和字符設備的區別在於,字符設備

驅動註冊設備節點,同時open, write可以直接操作字符設備, 而塊設備的註冊,並不是直接給write用的,而是給文件系統用的,文件系統註冊到掛載點上,掛載點去操作文件,塊設備,不像字符設備驅動一樣,可以單個字節處理,而flash的特性是扇區處理,一般SD卡扇區是512字節

 

union inode_ops_u
{
  FAR const struct file_operations     *i_ops;    /* Driver operations for inode *//
  FAR const struct mountpt_operations  *i_mops;   /* Operations on a mountpoint */
};
 
//掛載文件系統
if mount -t vfat /dev/mmcsd0 /fs/microsd
 
//mount.c 
mountpt_inode->u.i_mops  = mops; //文件系統和設備節點掛載好

 
 
3.6 sd卡文件的write操作

這個sd卡文件的操作流程, posix的write調用文件系統fat_write,再調用驅動的mmcsd_write

ssize_t write(int fd, FAR const void *buf, size_t nbytes)
      //fat文件系統接口的write
          ret = fat_hwwrite(fs, userbuffer, ff->ff_currentsector, nsectors);
            //調用mmcsd卡的驅動接口
            ->  ssize_t nSectorsWritten =
                 inode->u.i_bops->write(inode, buffer, sector, nsectors);
                 ->mmcsd_write,/* mmcsd 寫*/

 

通過以上的分析,我們的fread和fwrite,還用printf都是帶緩衝,fwrite寫的內容被刷新到sd卡的條件是,緩衝區滿了,纔會寫到物理sd, fread是每次從物理扇區讀取緩衝區的數據,比如,緩衝是32字節,fread要獲取10字節,fread也直接讀取32字節,緩衝到文件的緩衝區了,剩下的22字節,下次如果用戶調用fread的時候,讀取10字節,10字節小於緩衝區剩餘的22字節,可以直接從緩衝區讀走,這樣可以提高緩衝區的大小。printf具有字節獨立的特性,由於printf處理的都是字符,那麼高效的處理方法就是判斷是否接收到回車符號,就可以刷新緩衝區,而fgetc是,等待接收的數據中有了回車符號,才進行處理。

塊設備  【fwrite【c庫】->write【系統調用】->fat_write【文件系統】->mmcsd_write【SD驅動】】
字符設備 【fwrite【c庫】->write【系統調用】->serial_write【串口驅動】】
————————————————
版權聲明:本文爲CSDN博主「隨意的風」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Windgs_YF/article/details/87934532

發佈了4 篇原創文章 · 獲贊 167 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章