Linux下C語言實現檔案讀寫操作(包含在應用層與驅動層)

2020-08-10 10:34:44

前言:

第1節是筆者基於公司的「祖傳」C語言操作模板上做相應修改的程式,當做自己以後使用C語言檔案操作的模板。
第2節與第3節是筆者之前需要配合團隊內的小夥伴做數據轉換,他需要我將儲存在.txt檔案內的字元數據(十六進制)轉換爲二進制,也需要我將.Bin檔案內的二進制數據轉換爲字元數據(十六進制)。當然這兩節程式有很大的優化空間,弊端爲每次讀取數據的量太小,需要頻繁IO操作,導致執行速度變得很慢。解決方法爲使用malloc函數一次性將檔案的內容讀取到記憶體空間上,可大幅提高程式的執行速度。
第4節是筆者一直很好奇32位元操作系統下的C語言的數據型別所佔空間進行測試的例子,方便自己在後面的開發中準確的使用數據型別,不至於讓數據小而空間大,或者數據大而空間小。
第5節是筆者想通過C語言爲工程專案中修改組態檔中的參數,通過匹配組態檔中指定的變數去替換參數。但該節程式最後並未用於工程專案中,筆者想表達,C語言實現真的好麻煩,這就是更高階的程式語言強大之處。
第6節是筆者想爲嵌入式Linux產品的開機Logo動態讀取指定路徑下的.ppm圖片,原本考慮修改Kernel中顯示Logo部分,將圖片的數據寫入相應的地方。但後面在內核原始碼中找不到內核編譯時怎麼將logo_linux_clut224.ppm編譯爲logo_linux_clut224.c的方法,不清楚.ppm檔案是怎麼將數據轉化到struct linux_logo結構體中的clut和data成員,後面不得不放棄這種想法。但也讓筆者改變思路,讓驅動程式讀取指定路徑下的組態檔,讓驅動定向執行。
第7節爲應用層的常規檔案操作。
第8節爲獲取掛載在檔案系統下節點的儲存情況。
第9節摘抄自《Linux程式設計 第4版》中第3、4節的C語言進行檔案/目錄、系統操作
第10節爲記錄程式執行的時間

下面 下麪的程式參考了網上的許多例子,測試可用,若需要使用,需要修改部分參數。

1. C語言檔案操作的模板:

// 編譯與執行命令:
// gcc xxxx.c -o xxxx
// ./xxxx

#include <stdio.h>
#include <string.h>

int OpenFile(char *ReadFile, char *WriteFile, unsigned int *FileLength,
					FILE **fpRead, FILE **fpWrite);
void CloseFile(FILE *fpRead, FILE *fpWrite);
int FileOperation(unsigned int FileLength, FILE *fpRead, FILE *fpWrite);

int main(int argc, char *argv[])
{
	char ReadFile[] = "ReadFile.txt";
	char WriteFile[] = "WriteFile.txt";
	
	FILE *fpRead = NULL, *fpWrite = NULL;
	unsigned int FileLength = 0;
	
	int ret = 0;
	
	// 開啓需要讀、寫的檔案
	ret = OpenFile(ReadFile, WriteFile, &FileLength, &fpRead, &fpWrite);
	if(ret < 0)
	{
		return 0;
	}
	
	// 檔案操作
	FileOperation(FileLength, fpRead, fpWrite);
	
	// 關閉檔案
	CloseFile(fpRead, fpWrite);
	
	return 0;
}

int OpenFile(char *ReadFile, char *WriteFile, unsigned int *FileLength,
					FILE **fpRead, FILE **fpWrite)
{
	// 開啓需要讀的檔案
	*fpRead = fopen(ReadFile, "r+");
	if(*fpRead == NULL)
	{
		printf("open %s error \n", ReadFile);
		return -1;
	}
	
	// 開啓需要寫的檔案
	*fpWrite = fopen(WriteFile, "wb");
	if(*fpWrite == NULL)
	{
		printf("open %s error \n", WriteFile);
		fclose(*fpRead);
		return -1;
	}
	
	// 將讀檔案指針定位於檔案的末尾處,獲取檔案大小
	fseek(*fpRead, 0, SEEK_END);
	*FileLength = ftell(*fpRead);
	printf("FileLength = %dB (%.2fMB) \n", *FileLength,
					((double)*FileLength / (1024 * 1024)));
	
	return 1;
}

void CloseFile(FILE *fpRead, FILE *fpWrite)
{
	fclose(fpRead);
	fclose(fpWrite);
}

int FileOperation(unsigned int FileLength, FILE *fpRead, FILE *fpWrite)
{
	unsigned int i = 0;
	unsigned char Array[3];
	unsigned char CharLen = 2;
	
	// 將讀、寫檔案指針指向檔案頭
	fseek(fpRead, 0, SEEK_SET);
	fseek(fpWrite, 0, SEEK_SET);
	
	// 進行檔案讀寫
	for(i = 0; i < FileLength; i += 2)
	{
		if((i != 0) && (i % 16 == 0))
		{
			fwrite("\n", sizeof(unsigned char), 1, fpWrite);
		}
		
		memset(Array, '\0', sizeof(Array));
		
		fread(Array, sizeof(unsigned char), CharLen, fpRead);
		
		fwrite(Array, sizeof(unsigned char), CharLen, fpWrite);
		fwrite(" ", sizeof(unsigned char), 1, fpWrite);
	}
	
	return 1;
}
FILE結構體:
// 位於stdio.h,筆者在Linux底下找不到定義FILE結構體,只在Visual Studio內找到了定義
// 不同編譯器下的FILE結構體定義不一樣
// FILE結構是間接地操作系統的檔案控制塊 (FCB)來實現對檔案的操作

#ifndef _FILE_DEFINED
struct _iobuf {
        char *_ptr;       // 檔案輸入的下一個位置 
        int   _cnt;       // 當前緩衝區的相對位置 
        char *_base;      // 指基礎位置(即是檔案的其始位置)
        int   _flag;      // 檔案標誌
        int   _file;      // 應該是檔案描述符,進入開啓檔案列表索引的整數
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif

2. 將字元(十六進制)轉成二進制(C語言的檔案操作):

// 編譯與執行命令:
// gcc xxxx.c -o xxxx -lm
// ./xxxx

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
#include <errno.h>
#include <math.h>
#include <unistd.h> 
#include <sys/time.h>

int main(int argc, char *argv[])
{
	char FilePath[] = "/mnt/hgfs/XXXX/";
	char ReadFile[] = "File";
	char WriteFile[] = "FileConvert";
	char TxtUnit[] = ".txt";
	char BinUnit[] = ".bin";
	
	char ReadFilePath[80], WriteFilePath[80];
	char ClearWriteFileCmd[160], TxtToJPEGCmd[160];
	
	unsigned int FileLength = 0;
	
	char array[4];
	unsigned char DataBin = 0;
	unsigned char DataTemp = 0;
	
	int i = 0, j = 0;
	FILE *fpRead = NULL, *fpWrite = NULL;
	unsigned int NumRead = 0, NumWrite = 0;
	unsigned int DataCount = 0;
	int CharLen = 2;		// 每次讀取檔案的兩個字元
	
	memset(ReadFilePath, 0, 80 * sizeof(char));
	memset(WriteFilePath, 0, 80 * sizeof(char));
	memset(ClearWriteFileCmd, 0, 160 * sizeof(char));
	memset(TxtToJPEGCmd, 0, 160 * sizeof(char));
	
	sprintf(ReadFilePath, "%s%s%s", FilePath, ReadFile, TxtUnit);
	sprintf(WriteFilePath, "%s%s%s", FilePath, WriteFile, TxtUnit);
	sprintf(ClearWriteFileCmd, "dd if=/dev/null of=%s%s%s", FilePath, WriteFile, TxtUnit);
	sprintf(TxtToJPEGCmd, "mv %s%s%s %s%s%s", FilePath, WriteFile, TxtUnit, FilePath, WriteFile, BinUnit);
	
	// 開啓需要讀的檔案
	fpRead = fopen(ReadFilePath , "r+");
	if(fpRead == NULL)
	{
		printf("Open %s error \n", ReadFilePath);
		return -1 ;
	}
	// 開啓需要寫的檔案
	fpWrite = fopen(WriteFilePath , "wb");
	if(fpWrite == NULL)
	{
		printf("Open %s error \n", WriteFilePath);
		return -1 ;
	}
	
	printf("Open %s Success \n", ReadFilePath);
	printf("Open %s Success \n", WriteFilePath);
	
	system(ClearWriteFileCmd);
	
	// 定位於檔案的末尾處,獲取檔案大小
	fseek(fpRead, 0, SEEK_END);
	FileLength = ftell(fpRead);
	printf("FileLength = %d (%.2fK %.2fM) \n", FileLength, ((double)FileLength / 1024), ((double)FileLength / 1024 / 1024));
	
	// 將檔案指針指向檔案頭
	fseek(fpRead, 0, SEEK_SET);
	fseek(fpWrite, 0, SEEK_SET);
	
	NumRead = 0;
	NumWrite = 0;
	
	while(NumRead < FileLength)
	{
		memset(&array, '\0', 4 * sizeof(char));
		
		fseek(fpRead, NumRead, SEEK_SET);
		fread(array, sizeof(char), CharLen, fpRead);
		
		// 判斷取出的值是否爲換行符、空格、製表符、回車符
		//if((array[0] == '\n') || (array[0] == ' ') || (array[0] == '\t') || (array[0] == '\r'))
		if(array[0] <= ' ')
		{
			NumRead += (1 * sizeof(char));
			continue ;
		}
		
		NumRead += (CharLen * sizeof(char));
		
		// 下面 下麪這句話不能寫,會大大拖慢速度,執行速度降低150倍左右
		//fseek(fpWrite, NumWrite, SEEK_SET);
		
		// 字元轉爲數據
		for(DataBin = 0 , j = 0 ; j < CharLen ; j ++)
		{
			DataTemp = array[j] ;
			if((DataTemp >= '0') && (DataTemp <= '9'))		DataTemp = DataTemp - '0';
			else if((DataTemp >= 'a') && (DataTemp <= 'z'))	DataTemp = (DataTemp - 'a' + 10);
			else if((DataTemp >= 'A') && (DataTemp <= 'Z'))	DataTemp = (DataTemp - 'A' + 10);
			
			//DataBin += (DataTemp * pow(16, (CharLen - 1 - j)));
			DataBin += (DataTemp << (4 * (CharLen - 1 - j)));
		}
		fwrite(&DataBin, sizeof(char), 1, fpWrite);
		NumWrite += (1 * sizeof(char));
		DataCount ++ ;
		
	}
	
	fclose(fpRead);
	fclose(fpWrite);
	
	system(TxtToJPEGCmd);
	
	return 0 ;
}

3. 將二進制轉成字元(十六進制)(C語言的檔案操作):

// 編譯與執行命令:
// gcc xxxx.c -o xxxx -lm
// ./xxxx

#include <stdio.h>
#include <string.h>
#include <stdlib.h> 
#include <errno.h>
#include <math.h>
#include <unistd.h> 
#include <sys/time.h>

int FileConvert(char *FilePath, char *ReadFile, char *WriteFile);
int main(int argc, char *argv[])
{
	char FilePath[] = "/mnt/hgfs/Share_Ubuntu/FileDispose/Bin01/";
	char FileName[] = "rgb";
	char ReadFile[50], WriteFile[50];
	unsigned int FileCounter = 1, FileNum = 100;
	
	int ret = 0;
	
	while(FileCounter <= FileNum)
	{
		memset(ReadFile, 0, sizeof(ReadFile));
		memset(WriteFile, 0, sizeof(WriteFile));
		sprintf(ReadFile, "%s%02d", FileName, FileCounter);
		sprintf(WriteFile, "%sConvert", ReadFile);
		FileCounter++;
		
		ret = FileConvert(FilePath, ReadFile, WriteFile);
		if(ret < 0)
		{
			printf("Read and Write %s error \n", ReadFile);
			continue;
		}
		
	}
	
	return 0 ;
}

int FileConvert(char *FilePath, char *ReadFile, char *WriteFile)
{
	FILE *fpRead = NULL, *fpWrite = NULL;
	int CharLen = 1;		// 每次讀取檔案的一個字元
	unsigned char array[4];
	unsigned char DataBin = 0;
	unsigned char DataTemp = 0;
	int i = 0, j = 0;
	unsigned int DataCount = 0;
	
	char TxtUnit[] = ".txt", JpgUnit[] = ".jpg", BinUnit[] = ".bin";
	char ReadFilePath[80], WriteFilePath[80];
	char ClearWriteFileCmd[160];
	
	memset(ReadFilePath, 0, sizeof(ReadFilePath));
	memset(WriteFilePath, 0, sizeof(WriteFilePath));
	memset(ClearWriteFileCmd, 0, 160 * sizeof(char));
	
	sprintf(ReadFilePath, "%s%s%s", FilePath, ReadFile, BinUnit);
	sprintf(WriteFilePath, "%s%s%s", FilePath, WriteFile, TxtUnit);
	sprintf(ClearWriteFileCmd, "dd if=/dev/null of=%s > /dev/null 2>1", WriteFilePath);
	
	
	// 開啓需要讀的檔案
	fpRead = fopen(ReadFilePath , "r+");
	if(fpRead == NULL)
	{
		printf("Open %s error \n", ReadFilePath);
		return -1 ;
	}
	// 開啓需要寫的檔案
	fpWrite = fopen(WriteFilePath , "wb");
	if(fpWrite == NULL)
	{
		printf("Open %s error \n", WriteFilePath);
		return -1 ;
	}
	
	system(ClearWriteFileCmd);
	
	printf("Open %s Success \n", ReadFilePath);
	printf("Open %s Success \n", WriteFilePath);
	
	unsigned int FileLength = 0;
	
	// 定位於檔案的末尾處,獲取檔案大小
	fseek(fpRead, 0, SEEK_END);
	FileLength = ftell(fpRead);
	printf("(%s) FileLength = %d B (%.2f KB, %.2f MB) \n", ReadFile,
							FileLength,
							((double)(FileLength >> 10)),
							((double)(FileLength >> 20)));
	
	// 將檔案指針指向檔案頭
	fseek(fpRead, 0, SEEK_SET);
	fseek(fpWrite, 0, SEEK_SET);
	
	unsigned int NumRead = 0, NumWrite = 0;
	
	while(NumRead < FileLength)
	{
		if((NumRead != 0) && (NumRead % 3 == 0))
		{
			fwrite(" ", sizeof(char), 1, fpWrite);
		}
		
		memset(&DataBin, '\0', sizeof(DataBin));
		memset(array, '\0', sizeof(array));
		
		fread(&DataBin, sizeof(unsigned char), CharLen, fpRead);
		NumRead += (CharLen * sizeof(unsigned char));
		
		array[0] = (DataBin & 0xF0) >> 4;		// 提取十位
		array[1] = (DataBin & 0x0F) >> 0;		// 提取個位
		
//		printf("DataBin = 0x%X, array[0] = 0x%X, array[1] = 0x%X \n", DataBin, array[0], array[1]);
		
		// 二進制轉換爲字元的計算
		for(j = 0 ; j < 2 ; j ++)
		{
			if((array[j] >= 0x0) && (array[j] <= 0x9))		array[j] = array[j] + '0';
			else if((array[j] >= 0xA) && (array[j] <= 0xF))	array[j] = array[j] - 0xA + 'A';
		}
		
		fwrite(&array, sizeof(char), 2, fpWrite);
		
	}
	
	fclose(fpRead);
	fclose(fpWrite);
	
}

4. 驗證char系列、int系列、long系列、float系列、struct型別的儲存空間,數據大小端的儲存空間

當前目錄的檔案結構:
tree test/
test/
├── 1.h
├── a.c
├── b.c
├── c.c
└── Makefile

4.1 1.h檔案內容:
#ifndef __1_H
#define __1_H

#include <stdio.h>
#include <float.h>
#include <limits.h>

struct sData{
	unsigned int uint32_data_1;
	unsigned long long uint64_data_1;
	unsigned char uint8_data_1;
	unsigned char uint8_data_2;
	unsigned int uint32_data_2;
	unsigned short int uint16_data_1;
	unsigned char uint8_data_3;
	unsigned int uint32_data_3;
};

int b_fun(void);
int c_fun(void);

#endif
4.2 a.c檔案內容:
#include <stdio.h>
#include "1.h"

int main(int argc , char* argv[])
{
	b_fun();
	
	c_fun();
	
	return 0;
}

4.3 b.c檔案內容:
#include "1.h"
#include "string.h"

int b_fun(void)
{
	printf("\n== b_fun == \n");
	printf("\n============ char ============\n");
	printf("sizeof(char) = %d \n", sizeof(char));						// 1
	printf("sizeof(unsigned char) = %d \n", sizeof(unsigned char));		// 1
	printf("sizeof(signed char) = %d \n", sizeof(signed char));			// 1
	printf("\n============ short int ============\n");
	printf("sizeof(short int) = %d \n", sizeof(short int));						// 2
	printf("sizeof(unsigned short int) = %d \n", sizeof(unsigned short int));	// 2
	printf("sizeof(signed short int) = %d \n", sizeof(signed short int));		// 2
	printf("\n============ int ============\n");
	printf("sizeof(int) = %d \n", sizeof(int));						// 4
	printf("sizeof(unsigned int) = %d \n", sizeof(unsigned int));	// 4
	printf("sizeof(signed int) = %d \n", sizeof(signed int));		// 4
	printf("\n============ long int ============\n");
	printf("sizeof(long int ) = %d \n", sizeof(long int));						// 4
	printf("sizeof(unsigned long int) = %d \n", sizeof(unsigned long int));		// 4
	printf("sizeof(signed long int) = %d \n", sizeof(signed long int));			// 4
	printf("\n============ long long ============\n");
	printf("sizeof(long long) = %d \n", sizeof(long long));						// 8
	printf("sizeof(signed long long) = %d \n", sizeof(signed long long));		// 8
	printf("sizeof(unsigned long long) = %d \n", sizeof(unsigned long long));	// 8
	printf("\n============ float ============\n");
	printf("sizeof(float) = %d \n", sizeof(float));						// 4
	printf("\n============ double ============\n");
	printf("sizeof(double) = %d \n", sizeof(double));					// 8
	printf("\n\n");
	
	unsigned char uint8_Array[10];
	unsigned short int uint16_Array[10];
	unsigned int uint32_Array[10];
	unsigned long long uint64_Array[10];
	float float32_Array[10];
	double float64_Array[10];
	
	memset(uint8_Array, '\0', 10 * sizeof(unsigned char));
	memset(uint16_Array, '\0', 10 * sizeof(unsigned short int));
	memset(uint32_Array, '\0', 10 * sizeof(unsigned int));
	memset(uint64_Array, '\0', 10 * sizeof(unsigned long long));
	memset(float32_Array, '\0', 10 * sizeof(float));
	memset(float64_Array, '\0', 10 * sizeof(double));
	printf("sizeof(uint8_Array) = %d \n", sizeof(uint8_Array));
	printf("sizeof(uint16_Array) = %d \n", sizeof(uint16_Array));
	printf("sizeof(uint32_Array) = %d \n", sizeof(uint32_Array));
	printf("sizeof(uint64_Array) = %d \n", sizeof(uint64_Array));
	printf("sizeof(float32_Array) = %d \n", sizeof(float32_Array));
	printf("sizeof(float64_Array) = %d \n", sizeof(float64_Array));
	printf("\n\n");
	
	uint8_Array[0] = '1';
	printf("strlen(uint8_Array) = %d \n", strlen(uint8_Array));
	printf("\n\n");
	struct sData data1;
	printf("==~~ sizeof(data1) = %d, add sizeof( ... + ...) = %d ~~== \n", sizeof(data1),
			sizeof(data1.uint32_data_1) + sizeof(data1.uint8_data_1) +
			sizeof(data1.uint8_data_2) + sizeof(data1.uint32_data_2) +
			sizeof(data1.uint16_data_1) + sizeof(data1.uint8_data_3) +
			sizeof(data1.uint32_data_3) + sizeof(data1.uint64_data_1)
	);
	printf("\n\n");
	
	return 1;
}
4.4 c.c檔案內容:
#include "1.h"

int c_fun(void)
{
	printf("\n== c_fun == \n");
	printf("\n============ char min max dig ============\n");
	printf("CHAR_MIN( char MinValue ) = 0x%X \n", CHAR_MIN);
	printf("CHAR_MAX( char MaxValue ) = 0x%X \n", CHAR_MAX);
	printf("\n============ int min max dig ============\n");
	printf("INT_MIN( int MinValue ) = 0x%X \n", INT_MIN);
	printf("INT_MAX( int MaxValue ) = 0x%X \n", INT_MAX);
	printf("\n============ float min max dig ============\n");	// %E
	printf("FLT_MIN( float MinValue ) = %f \n", FLT_MIN);
	printf("FLT_MAX( float MaxValue ) = %f ( %E )\n", FLT_MAX, FLT_MAX);
	printf("FLT_DIG( float PrecisionValue ) = %d \n", FLT_DIG);
	printf("\n============ double min max dig ============\n");
	printf("DBL_MIN( double MinValue ) = %f \n", DBL_MIN);
	printf("DBL_MAX( double MaxValue ) = %f ( %E ) \n", DBL_MAX, DBL_MAX);
	printf("DBL_DIG( double PrecisionValue ) = %d \n", DBL_DIG);
	
	unsigned long long FloatConvert = 0;
	unsigned long long DoubleConvert = 0;
	FloatConvert = (unsigned long long)FLT_MAX;
	DoubleConvert = (unsigned long long)DBL_MAX;
	printf("FloatConvert = 0x%llX, DoubleConvert = 0x%llX \n\n", FloatConvert, DoubleConvert);
	
	unsigned int x = 0x87654321;		// 高位爲8,低位爲1
	unsigned char *p = (unsigned char *)&x;
	int i = 0;
	
	// 數據最左爲高位,最右爲低位
	// 小端模式:低地址低位數據,高地址高位數據
	// 大端模式:低地址高位數據,高地址低位數據
	printf("x = 0x%X  \n", x);
	for(i = 0; i < 4; i++)
	{
		printf("0x%X(Addr) = 0x%X \n", (unsigned int)(p + i), p[i]);
	}
	
	if(*p == 0x21)
	{
		printf("Little endian mode!\n\n");
	}
	else
	{
		printf("Big endian mode!\n\n");
	}
	
	return 1;
}
4.5 Makefile檔案內容:
# 目標檔案的執行平臺: ARM x86 MIPS RISC
#RUN_ARCH := x86
RUN_ARCH := ARM

# 目標檔名,輸入任意你想要的執行檔名
TARGET_FILE  := TestSizeof_MF
TARGET_PATH  := ~/nfs/

# 編譯工具
ifeq ($(RUN_ARCH), ARM)
	CROSS := /opt/gcc-linaro-arm-linux-gnueabihf-4.7-2012.11-20121123_linux/bin/arm-linux-gnueabihf-
else ifeq ($(RUN_ARCH), x86)
	CROSS := 
endif
# CROSS = arm-linux-gnueabihf-
export CC       = $(CROSS)gcc
export CXX      = $(CROSS)g++
export AR       := $(CROSS)ar
export AS       := $(CROSS)as
export STRIP    := $(CROSS)strip
export CPP      = $(CC) -E
export OBJCOPY  = $(CROSS)objcopy
export OBJDUMP  = $(CROSS)objdump
export NM       = $(CROSS)nm
export LD       = $(CROSS)ld

# 原始檔,自動找所有.c和.cpp檔案,並將目標定義爲同名.o檔案
SOURCE  := $(wildcard *.c) $(wildcard *.cpp)
OBJS    := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCE)))

# 編譯參數
LIBS    := #-L /Path -lname
LDFLAGS :=
DEFINES :=
INCLUDE := #-I /Path/include/
CFLAGS  := -g -Wall -O3 $(DEFINES) $(INCLUDE)
CXXFLAGS:= $(CFLAGS) -DHAVE_CONFIG_H


# 下面 下麪的基本上不需要做任何改動了
.PHONY : everything objs clean veryclean rebuild
everything : $(TARGET_FILE)
all : $(TARGET_FILE)
objs : $(OBJS)
rebuild: veryclean everything
clean :
	rm -rf *.o $(TARGET_FILE)
veryclean : clean
	rm -rf $(TARGET_FILE)

$(TARGET_FILE) : $(OBJS)
	$(CC) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LIBS)
# 應用程式需要刪掉的編譯中間檔案
	rm -rf *.o
# 拷貝目標檔案
	cp $(TARGET_FILE) $(TARGET_PATH)

5. 檔案內容替換/修改(舊字串與新字串大小不等長):

#if 0
程式思路:
1. 先比較新、舊字串的長度,判斷偏移位置的長度與方向
2. 查詢目標行,假如找到,則讀取目標行往後至文字結束的所有數據到Buffer
3. 替換目標行的數據
4. 將Buffer的數據匯入替換完目標行的數據後面
5. 假如舊字串數據比新字串數據長,則截斷多餘文字長度

筆者注:
該程式主要用於工程專案中修改組態檔中的參數,通過匹配組態檔中指定的變數去替換參數。
程式親測可用,替換的本質是對指定行的字串替換,字串的長度不限制,可過長也可過短,
也可不變,因此有一定的侷限性。
程式優點:執行過程中不建立新的文字,減少對NandFlash的擦除與進入。
後面過來學習的同學可以對程式進行修改,直到功能滿足自己的需求
#endif
# 原文字(程式執行前的)內容
$ cat ChangeFile.txt
[This is a Configuration File]
Parameter_A=0123456789
Parameter_B=0123456789
Parameter_C=0123456789
Parameter_D=0123456789
Parameter_E=0123456789
Parameter_F=0123456789
Parameter_G=0123456789

# 程式執行後的內容
$ cat ChangeFile.txt
[This is a Configuration File]
Parameter_A=0123456789
Parameter_B=0123456789
Parameter_C=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
Parameter_D=0123456789
Parameter_E=0123456789
Parameter_F=0123456789
Parameter_G=0123456789
// 程式:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

// 用於消除警告fgets、fread、ftruncate等函數的編譯警告
// 適合喜歡「0 errors, 0 warnings」的同學
char *pEliminateWarn = NULL;
int EliminateWarn = 0;

int OpenFile(char *ChangeFile, unsigned int *FileLength, FILE **fp);
int FileOperation(unsigned int FileLength, FILE *fp, char *OldStr,
					char *NewStr, int StrDiffer);
void CloseFile(FILE *fp);
void SetFileLength(char *ChangeFile, unsigned int FileLength, int StrDiffer);

int main(int argc, char *argv[])
{
	char ChangeFile[] = "ChangeFile.txt";
	char OldStr[] = "Parameter_C=0123456789";
	char NewStr[] = "Parameter_C=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	int StrDiffer = 0;			// OldStr 與 NewStr 之差

	FILE *fp = NULL;
	unsigned int FileLength = 0;
	int ret = 0;
	
	// StrDiffer > 0 說明文字後面的數據需要往前移動,且舊文字比新文字 長,
	//               文字末尾需要 截斷 StrDiffer 個字元長度;
	// StrDiffer < 0 說明文字後面的數據需要往後移動,且舊文字比新文字 短
	//               文字末尾需要 增長 StrDiffer 個字元長度;
	// StrDiffer = 0 說明文字後面的數據不需要移動,且舊文字與新文字 等長
	//               文字末尾 不變。
	StrDiffer = strlen(OldStr) - strlen(NewStr);
	
	// 1.開啓需要修改的檔案
	ret = OpenFile(ChangeFile, &FileLength, &fp);
	if(ret < 0)
	{
		return 0;
	}
	
	// 2.檔案操作
	FileOperation(FileLength, fp, OldStr, NewStr, StrDiffer);
	
	// 3.關閉檔案
	CloseFile(fp);
	
	// 4.如果(OldStr > NewStr),需截斷文字末尾的空餘,即設定文字長度
	if(StrDiffer > 0)
	{
		SetFileLength(ChangeFile, FileLength, StrDiffer);
	}
	
	return 0;
}

int OpenFile(char *ChangeFile, unsigned int *FileLength, FILE **fp)
{
	// 開啓需要修改的檔案
	*fp = fopen(ChangeFile, "rwb+");
	if(*fp == NULL)
	{
		printf("open %s error \n", ChangeFile);
		return -1;
	}
	
	// 將檔案指針定位於檔案的末尾處,獲取檔案大小
	fseek(*fp, 0, SEEK_END);
	*FileLength = ftell(*fp);
	printf("FileLength = %dB (%.2fMB) \n", *FileLength,
					((double)*FileLength / (1024 * 1024)));
	
	return 1;
}

void CloseFile(FILE *fp)
{
	fclose(fp);
}

void SetFileLength(char *ChangeFile, unsigned int FileLength, int StrDiffer)
{
	int fd = 0;
	unsigned int length = 0;
	
	fd = open(ChangeFile, O_RDWR);
	
	length = FileLength - StrDiffer;
	EliminateWarn = ftruncate(fd, length);
	
	printf("==== length = %d === \n", length);
	
	close(fd);
}

int FileOperation(unsigned int FileLength, FILE *fp, char *OldStr,
					char *NewStr, int StrDiffer)

{
	// 將檔案指針指向檔案頭
	fseek(fp, 0, SEEK_SET);
	
	// 進行相應的檔案操作
	unsigned int RemainFileLength = 0;		// 剩餘的長度
	unsigned int CurrentFileLength = 0; 	// 當前的長度
	
	unsigned char *RemainFileData = NULL;	// 剩下的文字數據
	
	char Array[500];		// 儲存文字的行數據
	int ArrayLength = 0;	// 記錄該行數據的長度
	
	while(1)
	{
		// 1.提取文字的行數據
		memset(Array, '\0', sizeof(Array));
		if(fgets(Array, sizeof(Array), fp) == NULL)
		{
			break;
		}
		
		// 2.記錄文字的行數據長度
		ArrayLength = strlen(Array);
		
		// 3.比較該行是否爲目標行
		if(strncmp(Array, OldStr, ArrayLength - 1) == 0)
		{
			// 4.定位文字中在OldStr後第1位(即「\n」)的位置
			CurrentFileLength = ftell(fp);
			
			// 5.讀取出文字中在OldStr後面不需要修改的文字數據 到 RemainFileData
			RemainFileLength = FileLength - CurrentFileLength;
			RemainFileData = (unsigned char *)malloc(RemainFileLength + 1);
			memset(RemainFileData, 0, (RemainFileLength + 1));
			EliminateWarn = fread(RemainFileData, sizeof(unsigned char), (RemainFileLength), fp);
			
			// 6.將檔案指針指向OldStr前第1位(即「\n」)的位置,將NewStr替換OldStr
			memset(Array, '\0', sizeof(Array));
			strcpy(Array, NewStr);
			fseek(fp, (CurrentFileLength - ArrayLength), SEEK_SET);
			fprintf(fp, "%s", Array);
			
			// 7.在文字中NewStr後第1位補上換行符(「\n」)
			fwrite("\n", sizeof(unsigned char), 1, fp);
			
			// 8.將RemainFileData中的數據匯入到文字NewStr後,並釋放RemainFileData
			fwrite(RemainFileData, sizeof(unsigned char), RemainFileLength, fp);
			free(RemainFileData);
			
			// 9.重新將檔案指針定位到一開始的位置
			fseek(fp, CurrentFileLength, SEEK_SET);
			
		}
		
	}
	
	return 1;
}

6. 驅動中的檔案操作:

6.1 程式部分:
// 筆者對內核態下的檔案操作只停留在驅動程式讀取文字內容,根據讀取組態檔的資訊,
// 驅動載入時進行定向執行,但深入的應用並不瞭解

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/slab.h>

int __init driver_file_init(void)
{
	mm_segment_t fs;
	
	struct file *fpRead = NULL, *fpWrite = NULL;
	struct kstat *StatRead = NULL;
	char *pFileBuf = NULL;
	
	loff_t PosRead = 0, PosWrite = 0;
	unsigned long long FileLength = 0;
	
	const char ReadFile[] = "/tmp/ReadFile.txt";
	const char WriteFile[] = "/tmp/WriteFile.txt";
	
	printk("driver_file enter \n");
	
	// 1.獲取內核態預設的記憶體地址限制,設定記憶體地址限制覆蓋到使用者空間
	fs = get_fs();
	set_fs(KERNEL_DS);
	
	// 2.獲取相應檔案的大小
	StatRead = (struct kstat *)kmalloc(sizeof(struct kstat), GFP_KERNEL);
	memset(StatRead, 0, sizeof(struct kstat));
	vfs_stat(ReadFile, StatRead);
	FileLength = StatRead->size;
	kfree(StatRead);
	
	// 3.開啓讀、寫檔案
	fpRead = filp_open(ReadFile, O_RDWR | O_CREAT, 0644);
	if(IS_ERR(fpRead))
	{
		printk("Open %s read file error \n", ReadFile);
		goto open_read_file_fail;
	}
	
	fpWrite = filp_open(WriteFile, O_RDWR | O_CREAT, 0644);
	if(IS_ERR(fpWrite))
	{
		printk("Open %s write file error \n", WriteFile);
		goto open_write_file_fail;
	}
	
	// 4.清空Buffer
	pFileBuf = (char *)kmalloc(FileLength * sizeof(char) + 1, GFP_KERNEL);
	memset(pFileBuf, 0, FileLength * sizeof(char) + 1);
	
	// 5.將讀檔案的內容匯入到Buffer,再把Buffer的內容匯入到寫檔案
	PosWrite = PosRead = 0;
	vfs_read(fpRead, pFileBuf, (FileLength * sizeof(char)), &PosRead);
	vfs_write(fpWrite, pFileBuf, (FileLength * sizeof(char)), &PosWrite);
	
	printk(KERN_INFO "==== pFileBuf = %s ==== \n", pFileBuf);
	
	// 6.釋放Buffer
	kfree(pFileBuf);
	
	// 7.關閉讀寫檔案
	filp_close(fpRead, NULL);
	filp_close(fpWrite, NULL);
	
	// 8.設定內核態的記憶體地址限製爲預設的記憶體地址限制
	set_fs(fs);
	
	return 0;
	
open_write_file_fail:
	filp_close(fpRead, NULL);
open_read_file_fail:
	set_fs(fs);
	return -1;
}

void __exit driver_file_exit(void)
{
	printk("driver_file exit \n");
}

module_init(driver_file_init);
module_exit(driver_file_exit);
MODULE_LICENSE("GPL");
6.2 對struct kstat結構體的解析:
struct kstat結構體位於 include/linux/stat.h,Linux 3.10.31-LTSI

struct kstat {
	u64				ino;		// inode number,inode節點號
	dev_t			dev;		// ID of device containing file,檔案所在裝置的ID
	umode_t			mode;		// protection,保護模式
	unsigned int	nlink;		// number of hard links,鏈向此檔案的連線數(硬連線)
	kuid_t			uid;		// user ID of owner,user id
	kgid_t			gid;		// group ID of owner,group id
	dev_t			rdev;		// device ID (if special file),裝置號,針對裝置檔案
	loff_t			size;		// total size, in bytes,檔案大小,位元組爲單位
	struct timespec		atime;		// time of last access,最近存取時間
	struct timespec		mtime;		// time of last modification,最近修改時間
	struct timespec		ctime;		// time of last status change,建立時間
	unsigned long		blksize;	// blocksize for filesystem I/O,系統塊的大小
	unsigned long long	blocks;		// number of blocks allocated,檔案所佔塊數
};

7. 常規操作中的檔案操作:

int Temp;

// 將檔案指針定位到檔案頭
fseek(fp, 0, SEEK_SET);

while(1)
{
	// 從指定的流 stream 獲取下一個字元(一個無符號字元),並把位置識別符號往前移動。
	Temp = fgetc(fp);
	
	// 該判斷只適合fgetc
	if(Temp == EOF)
	{
		// 把檔案流裡的所有未寫出數據立刻寫出
		fflush(fp);
		printf("\n");
		
		printf("File point have reached the end of file Or Read error \n");
		sleep(5);
	}
	
	// 該判斷具有通用性,測試一個檔案流的檔案尾標識
	if(feof(fp))
	{
		// 把檔案流裡的所有未寫出數據立刻寫出
		fflush(fp);
		printf("\n");
		
		printf("File point have reached the end of file \n");
		sleep(5);
	}
	
	printf("%c", Temp);
}

8. 獲取各個掛載點的儲存資訊操作:

8.1 程式部分:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/vfs.h>	// statfs、fstatfs函數
#include <mntent.h>		// setmntent、getmntent、endmntent函數

char MountPath[] = "/proc/mounts";	// proc系統中的mount檔案

struct sStoragreSize
{
	// 容量、使用、可用,單位爲 KB
	unsigned int Size_KB;
	unsigned int Used_KB;
	unsigned int Available_KB;
	
	// 掛載標誌位:爲0,沒有掛載;爲1,有掛載。
	unsigned char MountFlag;
};

void ReadStoragreSize(char *DevicePath, struct sStoragreSize *pStoragre)
{
	FILE *fpMount = NULL;
	struct mntent *mnt = NULL;
	struct statfs diskInfo;
	
	pStoragre->MountFlag = 0;
	
	// 開啓proc系統中的mount檔案
	fpMount = setmntent(MountPath, "r");
	if(fpMount == NULL)
	{
		printf("== Open %s error == \n", MountPath);
		return;
	}
	
	// 判斷是否有掛載相應的裝置,並標記有掛載的裝置
	while(1)
	{
		mnt = getmntent(fpMount);
		if(strncmp(mnt->mnt_dir, DevicePath, (sizeof(DevicePath)) - 1) == 0)
		{
			pStoragre->MountFlag = 1;
			break;
		}
	}
	endmntent(fpMount);
	
	if(pStoragre->MountFlag)
	{
		memset(&diskInfo, 0, sizeof(struct statfs));
		statfs(DevicePath, &diskInfo);
		
		// 一定要強制轉化爲unsigned long long(64位元),若地址超過4G,會導致數據溢位
		// 右移10位是爲了方便儲存數據,只需要用unsigned int(32位元)就可以儲存
		pStoragre->Size_KB = ((unsigned long long)diskInfo.f_bsize *
							(unsigned long long)diskInfo.f_blocks) >> 10;
		pStoragre->Used_KB = ((unsigned long long)diskInfo.f_bsize *
							(unsigned long long)(diskInfo.f_blocks -
							diskInfo.f_bfree)) >> 10;
		pStoragre->Available_KB = ((unsigned long long)diskInfo.f_bsize *
							(unsigned long long)diskInfo.f_bavail) >> 10;
	}
	else
	{
		pStoragre->Size_KB = pStoragre->Used_KB = pStoragre->Available_KB = 0;
	}
	
}

int main(int argc, char *argv[])
{
	int i = 0;
	char DevicePath[15];
	
	struct sStoragreSize Storagre;
	
	char MachinePath[] = "/home";		// 機身儲存的掛載路徑
	char SDCardPath[] = "/mnt/sdcard";	// SD卡的掛載路徑
	char UdiskPath[] = "/mnt/udisk";	// U盤的掛載路徑
	
	for(i = 0; i < 3; i++)
	{
		memset(DevicePath, 0, sizeof(DevicePath));
		memset(&Storagre, 0, sizeof(struct sStoragreSize));
		
		switch(i)
		{
			case 0:
			memcpy(DevicePath, MachinePath, sizeof(MachinePath));
			break;
			
			case 1:
			memcpy(DevicePath, SDCardPath, sizeof(SDCardPath));
			break;
			
			case 2:
			memcpy(DevicePath, UdiskPath, sizeof(UdiskPath));
			break;
		}
		
		ReadStoragreSize(DevicePath, &Storagre);
		if(Storagre.MountFlag)
		{
			printf("DevicePath = %s \n", DevicePath);
			
			printf("Size = %.3f GB \n", ((double)Storagre.Size_KB / (1024 * 1024)));
			printf("Used = %.3f GB \n", ((double)Storagre.Used_KB / (1024 * 1024)));
			printf("Available = %.3f GB \n\n", ((double)Storagre.Available_KB /
										(1024 * 1024)));
		}
		
	}
	
	return 0;
}
8.2 對struct statfs結構體的解析:
// struct statfs結構體位於 /usr/include/i386-linux-gnu/bits/statfs.h

struct statfs
{
	__fsword_t f_type;			// 檔案系統型別
	__fsword_t f_bsize;			// 經過優化的傳輸塊大小
#ifndef __USE_FILE_OFFSET64
	__fsblkcnt_t f_blocks;		// 檔案系統數據塊總數
	__fsblkcnt_t f_bfree;		// 可用塊數
	__fsblkcnt_t f_bavail;		// 非超級使用者可獲取的塊數
	__fsfilcnt_t f_files;		// 檔案結點總數
	__fsfilcnt_t f_ffree;		// 可用檔案結點數
#else
	__fsblkcnt64_t f_blocks;	// 檔案系統數據塊總數
	__fsblkcnt64_t f_bfree;		// 可用塊數
	__fsblkcnt64_t f_bavail;	// 非超級使用者可獲取的塊數
	__fsfilcnt64_t f_files;		// 檔案結點總數
	__fsfilcnt64_t f_ffree;		// 可用檔案結點數
#endif
	__fsid_t f_fsid;			// 檔案系統標識
	__fsword_t f_namelen;		// 檔名的最大長度
	__fsword_t f_frsize;
	__fsword_t f_flags;
	__fsword_t f_spare[4];		// spare for later
};
8.3 對struct stat結構體的解析:
// struct stat結構體位於 /usr/include/i386-linux-gnu/asm/stat.h

#ifdef __i386__

struct stat {
	unsigned long st_dev;			// 檔案的裝置編號
	unsigned long st_ino;			// 節點
	unsigned short st_mode;			// 檔案的型別和存取的讀寫執行許可權
	unsigned short st_nlink;		// 連到該檔案的硬連線數目,剛建立的檔案值爲1
	unsigned short st_uid;			// 使用者ID
	unsigned short st_gid;			// 組ID
	unsigned long st_rdev;			// (裝置型別)若此檔案爲裝置檔案,則爲其裝置編號
	unsigned long st_size;			// 檔案位元組數(檔案大小)
	unsigned long st_blksize;		// 塊大小(檔案系統的I/O 緩衝區大小)
	unsigned long st_blocks;		// 檔案所佔塊數
	unsigned long st_atime;			// 最後一次存取時間
	unsigned long st_atime_nsec;	// 
	unsigned long st_mtime;			// 最後一次修改時間
	unsigned long st_mtime_nsec;	// 
	unsigned long st_ctime;			// 最後一次改變時間(指屬性)
	unsigned long st_ctime_nsec;	// 
	unsigned long __unused4;		// 
	unsigned long __unused5;		// 
};

#else /* __i386__ */

struct stat {
	__kernel_ulong_t st_dev;
	 __kernel_ulong_t st_ino;
	__kernel_ulong_t st_nlink;

	unsigned int st_mode;
	unsigned int st_uid;
	unsigned int st_gid;
	unsigned int __pad0;
	__kernel_ulong_t st_rdev;
	__kernel_long_t st_size;
	__kernel_long_t st_blksize;
	__kernel_long_t st_blocks;		// 分配的512位元組塊數。

	__kernel_ulong_t st_atime;
	__kernel_ulong_t st_atime_nsec;
	__kernel_ulong_t st_mtime;
	__kernel_ulong_t st_mtime_nsec;
	__kernel_ulong_t st_ctime;
	__kernel_ulong_t st_ctime_nsec;
	__kernel_long_t __unused[3];
};

#endif
8.4 stat、fstat和lstat函數
stat、fstat和lstat函數:
https://www.cnblogs.com/xj626852095/p/3648237.html
8.5 對比內核態struct kstat結構體和使用者態struct stat結構體
# 共同點:
xx_ino			// inode節點號
xx_dev			// 檔案的裝置ID
xx_mode			// 檔案的型別和存取的讀寫執行許可權
xx_nlink		// 連到該檔案的硬連線數
xx_uid			// 使用者ID
xx_gid			// 組ID
xx_rdev			// 若此檔案爲裝置檔案,則爲其裝置編號
xx_size			// 檔案大小,位元組爲單位
xx_blksize		// 系統塊大小(檔案系統的I/O 緩衝區大小)
xx_blocks		// 檔案所佔塊數
xx_atime		// 最近存取時間,包含sec和nsec
xx_mtime		// 最近修改時間,包含sec和nsec
xx_ctime		// 建立時間,包含sec和nsec

# 不同點:
__unused_xx		// 沒有使用的

# 總結:
基本上是一樣的……

9. 目錄操作:待更中……

// 《Linux程式設計 第4版》,Neil Matthew、Richard Stones 著 的第3章與第4章

// 3.7 C語言檔案和目錄維護

// chmod 系統呼叫
int chmod(const char *path, mode_t mode);

// chown 系統呼叫
int chown();

// unlink、link和symlink系統呼叫
int unlink();
int link();
int symlink();

// mkdir和rmdir系統呼叫
int mkdir();
int rmdir();

// chdir系統呼叫和getcwd函數
int chdir();
char *getcwd();


// 3.8 C語言檔案和目錄維護

// opendir函數
DIR *opendir();

// readdir函數
struct dirent *readdir();

// telldir函數
long int telldir();

// seekdir函數
void seekdir();

// closedir函數
int closedir();


// 3.9 C語言錯誤處理

// strerror函數
char *strerror();

// perror函數
void perror();


// 3.10 /proc檔案系統


// 3.11 C語言fcntl和mmap函數

// 4

// 4.1 int main(int argc, char *argv[])

// 4.1.1 getopt函數
int getopt();

// 4.1.1 getopt_long函數
int getopt_long();

// 4.2 環境變數

char *getenv();

int putenv();

// 4.2.2 environ變數
extern char *environ;


// 4.3 時間和日期
time_t time();
double difftime();
struct tm *gmtime();
struct tm *localtime();
time_t mktime();
time_t asctime();
time_t ctime();
size_t strftime();
char *strptime();


// 4.4 臨時檔案
char *tmpnam();
FILE *tmpfile();
char *mktemp();
int mkstemp();


// 4.5 使用者資訊
uid_t getuid();
char *getlogin();

struct passwd *getpwuid();
struct passwd *getpwnam();

void endpwent();
struct passwd *getpwent();
void setpwent();

uid_t geteuid();

gid_t getgid();
gid_t getegid();
int setuid();
int setgid();


// 4.6 主機資訊
int gethostname();
int uname();
long gethostid();


// 4.7 日誌
void syslog();
void closelog();
void openlog();
int setlogmask();


// 4.8 資源和限制(硬體的限制、系統策略的限制、具體實現的限制)
int getpriority();	// 優先順序
int setpriority();
int getrlimit();	// 限制
int setrlimit();
int getrusage();	// 資源資訊

10. 時間記錄:

struct timeval TimeVal_1, TimeVal_2;

gettimeofday(&TimeVal_1, NULL);
// ***** 一頓操作 1 ***** 
// ***** 一頓操作 2 ***** 
// ***** 一頓操作 3 ***** 
gettimeofday(&TimeVal_2, NULL);

double Time1_s = (TimeVal_1.tv_sec) + ((double)TimeVal_1.tv_usec / (1000 * 1000));
double Time2_s = (TimeVal_2.tv_sec) + ((double)TimeVal_2.tv_usec / (1000 * 1000));
double Time1_ms = (TimeVal_1.tv_sec * 1000) + ((double)TimeVal_1.tv_usec / 1000);
double Time2_ms = (TimeVal_2.tv_sec * 1000) + ((double)TimeVal_2.tv_usec / 1000);
	
printf("Running %.2f min (%.2fs %.1f ms) \n\n", (Time2_s - Time1_s) / 60,
									(Time2_s - Time1_s), (Time2_ms - Time1_ms));