嵌入式linux FileSystem

文件系统


目标

类似ls的实现,如myls

ls [选项] [文件名…]

/etc/passwd、/etc/group

目录与文件

获取文件属性

  • stat 通过文件路径获取属性,面对符号链接文件时获取的是其所指向的目标文件的属性
  • fstat 通过文件描述符获取属性
  • lstat 面对符号链接文件时,获取的是符号链接文件的属性,而stat获取的是链接对象的属性
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

static off_t flen(const char *fname){
    struct stat statres;
    if (stat(fname,&statres) < 0) {
        perror("tata()");
        exit(1);
    }
    return statres.st_size;
}

int main(int argc,char **argv)
{
    if (argc < 2) {
        fprintf(stderr,"Usage...\n");
        exit(1);
    }

    long len = flen(argv[1]);
    printf("st_size = %ld\n",len);

    exit(0);
}

注意,在unix中文件大小size只是一个属性,不一定代表文件真正的大小(与文件系统相关)

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc,char **argv)
{
    if (argc < 2) {
        fprintf(stderr,"Usage...\n");
        exit(1);
    }

    int fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0600);
    if (fd < 0) {
        perror("open()");
        exit(1);
    }

    long err = lseek(fd,5LL*1024LL*1024LL*1024LL-1LL,SEEK_SET);
    if (err == -1) {
        perror("lseek");
        exit(1);
    }
	//偏移5G大小,计算时加上LL,转成long long型,防止类型溢出。
    write(fd,"",1);

    return 0;
}
// blkcnt_t  st_blocks;      /* number of 512B blocks allocated */
cp拷贝空文件,遇到'\0'不read,直接计数器记录下来,

文件访问权限

​ st_mode是一个16位的位图,用于表示文件类型,文件访问权限,及特殊权限位

  • _ _ _ (7种文件类型) _ t(粘住位) _ g+s _ u+s _ _ _ user _ _ _ group _ _ _ other共15位用16位的位图表示
    7种文件类型 b c d - l s p 不错的-老色批
  • b 块设备文件
  • c 字符设备文件
  • d 目录
  • “-” 常规文件
  • l 符号链接文件
  • s socket文件
  • p 匿名管道文件(不可见)

//文件类型
static int ftype(const char* fname) {
    if (stat(fname,&statres) < 0) {
        perror("rstat()");
        exit(1);
    }
    if (S_ISREG(statres.st_mode)) {
        return '-';
    }else if (S_ISDIR(statres.st_mode)) {
        return 'd';
    }else if(S_ISSOCK(statres.st_mode)){
        return 's';
    }else{
        return '?';
    }

umask

Linux 中umask的工作方式与chmod命令类似,它也用于定义文件或目录的权限。它们之间的区别在于chmod用于改变已有文件或目录的权限,而umask用于定义新建文件或目录的默认权限。

​ 0666 & ~umask

  • 防止产生权限过松的文件

文件权限的更改与管理

  • chmod (命令)

    'ugoa’控制哪些用户对该文件访问权限将被改变:文件的所有者(u),与文件所有者同组的用户(g),其他组的用户(o),所有用户(a).因此,a在这里等同于ugo.如果没有带参数,则缺省设置为a,运行效果相同,但是在umask中设
    置的位将不会受影响.

    • chmod a+x ??x ??x ??x
    • chmod u+x ??x ??? ???
    • chmod g+x ??? ??x ???
    • chmod o+x ??? ??? ??x
  • fchmod()

#include <sys/stat.h>
int chmod(const char *path,mode_t mode);
int fchmod(int fd,mode_t mode); //修改一个已经成功打开的文件

粘住位

  • t位
$ ls -l /
drwxrwxrwt   1 root root 3.6K 28 17:58 tmp

sticky bit  粘住位

如果一个可执行文件的这一位被设置了,那么在该程序第一次执行结束时,其程序的正文部分(机器指令部分)的一个副本仍被保存在交换区。下次执行该程序时能较快的装入内存。原因:交换区占用连续磁盘空间,可视为连续文件,而一个程序的正文部分在交换区也是连续存放的,而在一般的unix文件系统中,文件的各数据块很可能是随机存放的。对于常用的应用程序,常常设置他们所在文件的粘住位。
现今的unix系统大多 虚拟存储系统以及 快速文件系统,所以不再需要这种技术。
现今系统扩展了粘住位的使用范围:
如果对一个目录设置了粘住位,只有对该目录具有 写权限的用户在满足下列条件之一的情况下,才能删除或更名该目录下的文件:
拥有此文件
拥有此目录
是超级用户
目录/tmp和/var/spool/uucppublic设置了粘住位,任何用户都可以在这两个目录中创建文件。任用户对该两个目录的权限通常是r,w,x但是用户不能删除或更名属于其他人的文件。

文件系统的实质

FAT ,UFS

文件系统: 文件或数据的存储和管理

硬链接 符号链接

​ 硬链接是目录项的同义词,且建立硬链接有限制;不能给分区建立,不能给目录建立。符号链接优点:可跨分区,可以给目录建立。

  • link (命令) 创建硬链接 其实就是在目录项中添加一条映射
  • ln() => ln -在文件之间建立连接
  • unlink() 删除一个文件的硬连接 但并不是删除文件 只有当一个文件的硬链接数为0 且没有进程占用该文件时一个文件才有被删除的可能(数据可被随意改写)
  • remove
  • rename

文件读写时间

  • utime()

    int utime(const char *filename, const struct utimbuf *times);

    可更改文件的最后读的时间和最后修改的时间

目录的创建和销毁

  • mkdir ()
  • rmdir()

更改当前工作路径

  • chdir => cd

  • fchdir

  • getcwd //get
    current working directory

    int fchdir(int fd);

    int chdir(const char *path);

分析目录/读取目录内容

单独调用

  • glob 解析模式/通配符
*,?
//glob解析路径
static void Glob(){
    glob_t globres;
    int err = glob(PAT,0,&errfunc,&globres);
    if (err) {
        printf("Error code = %d\n",err);
    }

    for (int i = 0;globres.gl_pathv[i]!= NULL;i++) {
        fprintf(stdout,"%s\n",globres.gl_pathv[i]);
    }
}

#include<stdio.h>
#include<stdlib.h>
#include <glob.h>
#define PAT "/etc/a*.conf"
//#define PAT "/etc/.*" //隐藏文件
#if 0
static int errfunc_(const char *errpath,int eerrno){
    puts(errpath);
    fprintf(stderr,"ERROR MSG:%s\n",strerror(eerror()));
    return 0;
}
#endif
int main(int argc,char *argv[]){

    int err,i;
    glob_t golbres;

    err=glob(PAT,0,NULL,&golbres);
    if(err){
        printf("ERROR code=%d\n",err);
        exit(1);
    } 
    for(i=0;i<golbres.gl_pathc;i++){
        puts(golbres.gl_pathv[i]);
    }

    exit(0);
}

组合调用

  • opendir()
  • closedir()
  • readdir()
  • rewenddir()
  • seekdir()
  • telldir()
//组合解析路径
static void PathParse(char *Path) {
    DIR *dp;
    struct dirent *cur;

    dp = opendir(Path);
    if (dp == NULL) {
          perror("opendir");
          exit(1);
    }

    while((cur = readdir(dp)) != NULL) {
        fprintf(stdout,"%s ",cur->d_name);
        fprintf(stdout,"type:%d ",cur->d_type);
    }

    closedir(dp);
}

    //getcwd()的使用
    char pwd[1024];
    getcwd(pwd,1024);
    fprintf(stdout,"%s\n",pwd);
    PathParse(pwd);

Linux du (英文全拼:disk usage)命令用于显示目录或文件的大小。

du 会显示指定的目录或文件所占用的磁盘空间。

stat、du和ls命令查询的文件大小不一致,是因为它们计算文件大小的方式不同。

stat命令返回的是文件的字节数(即文件大小)。
du命令返回的是文件实际在磁盘上占用的大小(即磁盘使用情况大小)。
ls命令返回的是文件大小的近似值,通常以千字节为单位(即文件大小除以1024)。

  • 综合案例 mydu.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glob.h>
#include <string.h>

#define PATHSIZE 1024

static int path_noloop(const char *path) {
    char *pos;
    pos = strrchr(path,'/');
    if (pos == NULL) {
        exit(1);
    }

    if (strcmp(pos+1,".") == 0||strcmp(pos+1,"..")== 0) {
        return 0;
    }
    return 1;
}

static int64_t mydu(const char *path) {
    static struct stat statres;
    static char nextpath[PATHSIZE];
    glob_t globres;
    int64_t sum = 0;

    //非目录
    if (lstat(path,&statres) < 0) {
        perror("lstat()");
        exit(1);
    }

    if (!S_ISDIR(statres.st_mode)){
        fprintf(stdout,"%ld\t%s\n",statres.st_blocks / 2,path);
        return statres.st_blocks;
    }
    //目录
    //拼接路径
    strncpy(nextpath,path,PATHSIZE);
    strncat(nextpath,"/*",PATHSIZE);
    if (glob(nextpath,0,NULL,&globres) < 0) {
        fprintf(stderr,"glob()");
        exit(1);
    }

    strncpy(nextpath,path,PATHSIZE);
    strncat(nextpath,"/.*",PATHSIZE);
    if (glob(nextpath,GLOB_APPEND,NULL,&globres) < 0) {
        fprintf(stderr,"glob()");
        exit(1);
    }

    sum = statres.st_blocks;
    for (int i = 0;i < globres.gl_pathc;i++){
        if (path_noloop(globres.gl_pathv[i]))
            sum += mydu(globres.gl_pathv[i]);
    }
    
    globfree(&globres);//回收资源
    return sum;
        
}

int main(int argc,char **argv)
{   
    if (argc < 2) {
        fprintf(stderr,"%s\n","Usage...");
        exit(1);
    }
    
    printf("%ld\t%s\n",mydu(argv[1])/2,argv[1]);

    return 0;
}

系统数据文件和信息

在Linux /etc/passwd文件中每个用户都有一个对应的记录行,它记录了这个用户的一些基本属性。系统管理员经常会接触到这个文件的修改以完成对用户的管理工作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类似于下面的例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从上面的例子我们可以看到,/etc/passwd中一行记录对应着一个用户,每行记录又被冒号(:)分隔为7个字段,其格式和具体含义如下:

  1. /etc/passwd 账户信息

    • getpwuid();
    • getpwnam();
  2. /etc/group 组信息

    • getgrgid();
    • getgrgrnam();
  3. /etc/shadow 加密口令

    • getspnam();

    • crypt(); //加密

      char *crypt(const char *key, const char *salt);

      salt参数要的是( 6 6 6fMVz3d6XAGZxRmN2$)这种字符串

    • getpass();

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    cib: 6 (加密方式) 6(加密方式) 6(加密方式)fMVz3d6XAGZxRmN2(杂质串)$xTUGOARyxLuMZe7qySQs5E8fAuAGZgOR9.KLk‐
    WoIqRMSC5znsLhashESRaHVpFSZgnbGzhtiMJhUod1eacy7l.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <shadow.h>
#include <string.h>

int main(int argc,char **argv)
{
    char *input_passwd;//来自命令行的密码原文
    char *crypted_passwd;
    struct spwd *shadowline;
    
    if (argc < 2) {
        fprintf(stderr,"Usage...\n");
        exit(1);
    }

    input_passwd = getpass("PassWoed:");

    shadowline = getspnam(argv[1]);

    crypted_passwd = crypt(input_passwd,shadowline->sp_pwdp);
    
    if (strcmp(shadowline->sp_pwdp,crypted_passwd) == 0)
      puts("ok!");
    else
      puts("failed!");

    return 0;
}

以上代码编译后(编译要加`-lcryp`) ,要使用root用户执行(普通用户没有权限)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <crypt.h>
#include <shadow.h>
#include <unistd.h>

int main(int argc,char *argv[]){

    //struct passwd *pwdline;
    char *cryptd;
    char *input_pass;
    struct spwd *shadow;

    if(argc<2){
        fprintf(stderr,"Usgae....\n");
        exit(1);
    }
    //加密
    input_pass= getpass("Password:");
    shadow=getspnam(argv[1]);
    cryptd=crypt(input_pass,shadow->sp_pwdp);

    if(strcmp(shadow->sp_pwdp,cryptd)==0){
      puts("ok!");  
    }else{
        puts("No!");
    }

    //pwdline=getpwuid(atoi(argv[1]));
    //printf("getpwuid===>%s\n",pwdline->pw_name);
    exit(0);
}

时间戳

time_t => char * => struct_tm

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • time() 从kernel中取出时间戳(以秒为单位)
  • gntime() 将时间戳转换为struct_tm 格林威治时间
  • localtime() 将时间戳转换为struct_tm 本地时间
  • mktime() 将struct_tm结构体转换为时间戳,还可以检查是否溢出
  • strftime(); 格式化时间字符串
time_t stamp;
struct tm *tm;
time(&stamp);
stamp = time(NULL);

tm = localtime(&stamp);

strftime(buf,BUFSIZE,"%Y-%m-%d",tm);
puts(buf);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#define BUFSIZE 1024

int main()
{

    char fmttime[BUFSIZ];
    int count = 0;

    FILE *fptr = fopen("./log","a+");
    if (fptr == NULL) {
        perror("fopen()");
        exit(1);
    }

    char buf[BUFSIZE];

    while(fgets(buf,BUFSIZE,fptr) != NULL){
        count++;
    }

    char res[BUFSIZE];

    while (1){
        time_t stamp;
        stamp = time(NULL);

        struct tm *struct_tm;
        struct_tm = localtime(&stamp);

        strftime(fmttime,BUFSIZE,"%Y-%m-%d %H:%M:%S",struct_tm);
        fprintf(fptr,"%d %s\n",++count,fmttime);
        fflush(fptr);
        sleep(1);
    }

    fclose(fptr);

    exit(0);
}
//tm->tm_mday+=100;
//(void)mktime(tm);//自动调整时间进位

进程环境

main函数

  • int main(int argc,char **argv)

进程的终止

  • 正常终止
    • 从main函数返回
    • 调用exit
    • 调用_exit或者_Exit(系统调用)
    • 最后一个线程从其启动例程返回
    • 最后一个线程调用pthread_exit
钩子函数
All functions registered with atexit(3) and on_exit(3) are called,in the reverse order of their registration.
  • atexit()

在这里插入图片描述

  • 异常终止

    • 调用abort

      C 库函数 void abort(void) 中止程序执行,直接从调用的地方跳出。

    • 接到一个信号并终止

    • 最后一个线程对其取消请求作出响应

命令行参数的分析

​ getopt();getopt_long();

//#define FMTSTRSIZE 1024

char fmtstr[FMTSTRSIZE];
fmtstr[0]='\0';
//解析命令行
while(1) {
    c = getopt(argc,argv,"lt-a"); // - 识别非选项的传参,识别lta;
    if (c < 0){
        break;
    }
    
    switch (c){
        case 'l':
            f.filesize = flen(argv[1]);
            strncat(fmtstr,"filesize:%ld ",FMTSTRSIZE-1);
            break;
        case 't':
            f.filetype = ftype(argv[1]);
            strncat(fmtstr,"filetype:%c ",FMTSTRSIZE-1);
            break;
        case 'a':
            PathParse(argv[optind]);
            break;
        default:
            break;
    }
}

环境变量

本质就是 KEY = VALUE

  • export
  • getenv()
  • setenv()
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

static void getEnv(char *key){
    puts(getenv(key));
}

int main()
{
    
    for (int i = 0;environ[i] != NULL;i++){
        puts(environ[i]);
    }
    getEnv("ZSH");
    return 0;
}

C程序的存储空间布局

  • pmap (1)

#ifndef LLIST_H__
#define LLIST_H__
enum{
    F = 1,
    B = 2,
};

//普通节点
struct llist_node_st{
    void *data;
    struct llist_node_st *prev;
    struct llist_node_st *next;
};
//头节点
typedef struct {
    int size;
    struct llist_node_st head;
} LLIST; //LLIST就是一个双向链表的头节点类型,对于链表的操作都是用head来进行的

//传入 每个数据节点的数据类型大小
LLIST *llist_careate(int size);
//传入 一个已经创好的链表的头节点,插入的数据,插入的模式
int llist_insert(LLIST *,const void *data,int mode);
//传入
void *llist_find(LLIST *head,const void* ,int (*func)(const void*,const void*));
//
int llist_delete(LLIST *head,const void* ,int (*func)(const void*,const void*));
//
int llist_fetch(LLIST *head,const void* ,int (*func)(const void*,const void*),void *);
//传入 一个已经创建好的链表头节点
void llist_travel(LLIST* head,void (*func)(const void*));
void llist_destroy(LLIST *);

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

#include "llist.h"


//传入 每个数据节点的数据类型大小
LLIST *llist_careate(int size){
    LLIST *new;
    new = malloc(sizeof(*new));
    if (new == NULL){
        return NULL;
    }

    new->size = size;
    new->head.data = NULL;
    new->head.prev = &new->head;
    new->head.next = &new->head;
    
    return new;
}
//传入 一个已经创好的链表的头节点,插入的数据,插入的模式
int llist_insert(LLIST *head,const void *data,int mode){
    struct llist_node_st *newnode;
    newnode = malloc(sizeof(*newnode));
    if (newnode == NULL)
      return -1;

    newnode->data = malloc(head->size);
    if (newnode->data == NULL){
        return -2;
    }
    memcpy(newnode->data,data,head->size);

    switch (mode) {
        case F:
            newnode->prev = &head->head;
            newnode->next = head->head.next;
            break;
        case B:
            newnode->prev = head->head.prev;
            newnode->next = &head->head;
            break;
        default:
            return -3;
    }

    newnode->prev->next = newnode;
    newnode->next->prev = newnode;

    return 0;

}
//传入 一个已经创建好的链表头节点,一个辅助遍历函数
void llist_travel(LLIST* head,void (*func)(const void *)){
    struct llist_node_st *cur,*next;
    for (cur = head->head.next;cur != &head->head;cur = next) {
        func(cur->data);
        next = cur->next;
    }
}

//辅助函数
static struct llist_node_st *find_(LLIST *head,const void *key,int (*func)(const void *,const void *)){
    struct llist_node_st *cur;
    for (cur = head->head.next;cur != &head->head;cur = cur->next){
        if (func(key,cur->data) == 0){
            return cur;
        }
    }
    return &head->head;
}

void *llist_find(LLIST *head,const void* key,int (*func)(const void*,const void*)){
    return find_(head,key,func)->data;
##### 静态库
}

//
int llist_delete(LLIST *head,const void* key,int (*func)(const void*,const void*)){
    struct llist_node_st *node;
    node = find_(head,key,func);
    if (node == &head->head){
        return -1;
    }else {
        node->prev->next = node->next;
        node->next->prev = node->prev;
        free(node->data);
        free(node);
        return 0;
    }
}
//
int llist_fetch(LLIST *head,const void* key,int (*func)(const void*,const void*),void *data){
    struct llist_node_st *node;
    node = find_(head,key,func);
    if (node == &head->head){
        return -1;
    }else {
        node->prev->next = node->next;
        node->next->prev = node->prev;
        data = node->data;
        free(node->data);
        free(node);
        return 0;
    }
}

void llist_destroy(LLIST *head) {
    struct llist_node_st *cur,*next;

    for (cur = head->head.next;cur != &head->head;cur = next) {
        next = cur->next;
        free(cur->data);
        free(cur);
    }
    free(head);
}
CFLAGS		+=-Wall -g -lstdc++ -D_FILE_OFFSET_BITS=64
CC			=gcc
TARGET		=DoubleLinkList
OBJ			=llist.o
src  		=llist.c

$(TARGET):$(OBJ)
	$(CC) main.c $(OBJ) -o $@

$(OBJ):$(src)
	$(CC) $(src) $(CFLAGS) -c -o $@
	
clean:
	-rm -f $(OBJ)
	-rm -f $(TARGET)
静态库
  • libxx.a xx指代库名
  • ar -cr libxx.a yyy.o
  • 发布到 /usr/local/include /usr/local/lib
  • 编译 gcc -L/usr/local/lib -o main mian.o -lxx -l参数必须在最后,有依赖
make
ar -cr libllist.a llist.o 
 gcc -L./ -o main main.c -lllist 
动态库
  • libxx.so xx是库名
  • gcc -shared -fpic -o libxx.so yyy.c
  • 发布到 /usr/local/include /usr/local/lib (.h 与 .so)
  • /sbin/ldconfig 重读 /etc/ld.so.conf
  • gcc -I/usr/local/include -L/usr/local/lib -o ... lxx

重名用动态库

  • 非root用户发布
    • sp xx.so ~/lib
    • export LD_LIBRARY_PATH=~/lib

函数跳转

适用场景: 在树结构中查找元素,找到后直接回到第一次调用处(跨函数),不用一层一层返回
  • setjmp()
  • longjmp()
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static jmp_buf save;

static void d(){
    printf("%s is called\n",__FUNCTION__);
    longjmp(save,2);
    printf("%s is returned\n",__FUNCTION__);
}


static void c(){
    printf("%s is called\n",__FUNCTION__);
    d();
    printf("%s is returned\n",__FUNCTION__);
}


static void b(){
    printf("%s is called\n",__FUNCTION__);
    c();
    printf("%s is returned\n",__FUNCTION__);
}

static void a(){
    int ret = setjmp(save);
    if  (ret == 0) {
        printf("%s is called\n",__FUNCTION__);
        b();
        printf("%s is returned\n",__FUNCTION__);
    }else {
        printf("code %d return \n",ret);
    }
}

int main()
{
    a();
    return 0;
}

资源的获取与控制

  • getrlimit
  • setrlimit

_PATH=~/lib

函数跳转

适用场景: 在树结构中查找元素,找到后直接回到第一次调用处(跨函数),不用一层一层返回
  • setjmp()
  • longjmp()
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static jmp_buf save;

static void d(){
    printf("%s is called\n",__FUNCTION__);
    longjmp(save,2);
    printf("%s is returned\n",__FUNCTION__);
}


static void c(){
    printf("%s is called\n",__FUNCTION__);
    d();
    printf("%s is returned\n",__FUNCTION__);
}


static void b(){
    printf("%s is called\n",__FUNCTION__);
    c();
    printf("%s is returned\n",__FUNCTION__);
}

static void a(){
    int ret = setjmp(save);
    if  (ret == 0) {
        printf("%s is called\n",__FUNCTION__);
        b();
        printf("%s is returned\n",__FUNCTION__);
    }else {
        printf("code %d return \n",ret);
    }
}

int main()
{
    a();
    return 0;
}

资源的获取与控制

  • getrlimit
  • setrlimit
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值