您的位置:首页 > 游戏 > 游戏 > 鸿蒙内核源码分析(文件句柄篇) | 你为什么叫句柄

鸿蒙内核源码分析(文件句柄篇) | 你为什么叫句柄

2025/1/7 19:01:57 来源:https://blog.csdn.net/u012165769/article/details/141464616  浏览:    关键词:鸿蒙内核源码分析(文件句柄篇) | 你为什么叫句柄

句柄 | handle

int open(const char* pathname,int flags);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int close(int fd);

只要写过应用程序代码操作过文件不会陌生这几个函数,文件操作的几个关键步骤嘛,跟把大象装冰箱分几步一样.先得把冰箱门打开,再把大象放进去,再关上冰箱门.其中最重要的一个参数就是fd,应用程序所有对文件的操作都基于它.fd可称为文件描述符,或者叫文件句柄(handle),个人更愿意称后者. 因为更形象,handle英文有手柄的意思,跟开门一样,握住手柄才能开门,手柄是进门关门的抓手.映射到文件系统,fd是应用层出入内核层的抓手.句柄是一个数字编号, open | creat去申请这个编号,内核会创建文件相关的一系列对象,返回编号,后续通过编号就可以操作这些对象.原理就是这么的简单,本篇将从fd入手,跟踪文件操作的整个过程.

请记住,鸿蒙内核中,在不同的层面会有两种文件句柄:

  • 系统文件句柄(sysfd),由内核统一管理,和进程文件句柄形成映射关系,一个sysfd可以被多个profd映射,也就是说打开一个文件只会占用一个sysfd,但可以占用多个profd,即一个文件被多个进程打开.
  • 进程文件句柄(profd),由进程管理的叫进程文件句柄,内核对不同进程中的fd进行隔离,即进程只能访问本进程的fd.举例说明之间的关系:
    文件            sysfd     profd吃个桃桃.mp4        10    13(A进程)吃个桃桃.mp4        10    3(B进程)容嬷嬷被冤枉.txt    12    3(A进程)容嬷嬷被冤枉.txt    12    3(C进程)

进程文件句柄

在鸿蒙一个进程默认最多可以有256fd,即最多可打开256个文件.文件也是资源的一种,系列篇多次说过进程是管理资源的,所以在进程控制块中能看到文件的影子files_structfiles_struct可理解为进程的文件管理器,里面只放和本进程相关的文件,线程则共享这些文件.另外子进程也会拷贝一份父进程的files_struct到自己的files_struct上,在父子进程篇中也讲过fork的本质就是拷贝资源,其中就包括了文件内容.

//进程控制块
typedef struct ProcessCB {//..#ifdef LOSCFG_FS_VFSstruct files_struct *files;        /**< Files held by the process */ //进程所持有的所有文件,注者称之为进程的文件管理器#endif	//每个进程都有属于自己的文件管理器,记录对文件的操作. 注意:一个文件可以被多个进程操作
} LosProcessCB;
struct files_struct {//进程文件表结构体int count;				//持有的文件数量struct fd_table_s *fdt; //持有的文件表unsigned int file_lock;	//文件互斥锁unsigned int next_fd;	//下一个fd
#ifdef VFS_USING_WORKDIRspinlock_t workdir_lock;	//工作区目录自旋锁char workdir[PATH_MAX];		//工作区路径,最大 256个字符
#endif
};

fd_table_sfiles_struct的成员,负责记录所有进程文件句柄的信息,个人觉得鸿蒙这块的实现有点乱,没有封装好.

struct fd_table_s {//进程fd表结构体unsigned int max_fds;//进程的文件描述符最多有256个struct file_table_s *ft_fds; /* process fd array associate with system fd *///系统分配给进程的FD数组 ,fd 默认是 -1fd_set *proc_fds;	//进程fd管理位,用bitmap管理FD使用情况,默认打开了 0,1,2	       (stdin,stdout,stderr)fd_set *cloexec_fds;sem_t ft_sem; /* manage access to the file table */ //管理对文件表的访问的信号量
};

file_table_s 记录进程fd和系统fd之间的绑定或者说映射关系

struct file_table_s {//进程fd <--> 系统fd绑定intptr_t sysFd; /* system fd associate with the tg_filelist index */
};

fd_set实现了进程fd按位图管理,系列操作为 FD_SET,FD_ISSET,FD_CLR,FD_ZERO
除以8是因为 char类型占8bit位.请尝试去理解下按位操作的具体实现.

typedef struct fd_set
{unsigned char fd_bits [(FD_SETSIZE+7)/8];
} fd_set;
#define FD_SET(n, p)  FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] = (u8_t)((p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] |  (1 << (((n)-LWIP_SOCKET_OFFSET) & 7))))
#define FD_CLR(n, p)  FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] = (u8_t)((p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] & ~(1 << (((n)-LWIP_SOCKET_OFFSET) & 7))))
#define FD_ISSET(n,p) FDSETSAFEGET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] &   (1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_ZERO(p)    memset((void*)(p), 0, sizeof(*(p)))

vfs_procfd.c 为进程文件句柄实现文件,每个进程的 0,1,2 号 fd是由系统占用并不参与分配,即为大家熟知的:

  • STDIN_FILENO(fd = 0) 标准输入 接收键盘的输入
  • STDOUT_FILENO(fd = 1) 标准输出 向屏幕输出
  • STDERR_FILENO(fd = 2) 标准错误 向屏幕输出
/* minFd should be a positive number,and 0,1,2 had be distributed to stdin,stdout,stderr */if (minFd < MIN_START_FD) {minFd = MIN_START_FD;}
//分配进程文件句柄
static int AssignProcessFd(const struct fd_table_s *fdt, int minFd)
{if (fdt == NULL) {return VFS_ERROR;}if (minFd >= fdt->max_fds) {set_errno(EINVAL);return VFS_ERROR;}//从表中搜索未使用的 fd/* search unused fd from table */for (int i = minFd; i < fdt->max_fds; i++) {if (!FD_ISSET(i, fdt->proc_fds)) {return i;}}set_errno(EMFILE);return VFS_ERROR;
}
//释放进程文件句柄
void FreeProcessFd(int procFd)
{struct fd_table_s *fdt = GetFdTable();if (!IsValidProcessFd(fdt, procFd)) {return;}FileTableLock(fdt);FD_CLR(procFd, fdt->proc_fds);	//相应位清0FD_CLR(procFd, fdt->cloexec_fds);fdt->ft_fds[procFd].sysFd = -1;	//解绑系统文件描述符FileTableUnLock(fdt);
}
  • 分配和释放的算法很简单,由位图的相关操作完成.
  • fdt->ft_fds[i].sysFd中的i代表进程的fd,-1代表没有和系统文件句柄绑定.
  • 进程文件句柄和系统文件句柄的意义和关系在 (VFS篇)中已有说明,此处不再赘述,请自行前往翻看.

系统文件句柄

系统文件句柄的实现类似,但它并不在鸿蒙内核项目中,而是在NuttX项目的 fs_files.c 中, 因鸿蒙内核项目中使用了其他第三方的项目,所以需要加进来一起研究才能看明白鸿蒙整个内核的完整实现.具体涉及的子系统仓库如下:

  • 子系统注解仓库

在给鸿蒙内核源码加注过程中发现仅仅注解内核仓库还不够,因为它关联了其他子系统,若对这些子系统不了解是很难完整的注解鸿蒙内核,所以也对这些关联仓库进行了部分注解,这些仓库包括:

  • 同样由位图来管理系统文件句柄,具体相关操作如下
//用 bitmap 数组来记录文件描述符的分配情况,一位代表一个SYS FD
static unsigned int bitmap[CONFIG_NFILE_DESCRIPTORS / 32 + 1] = {0};
//设置指定位值为 1
static void set_bit(int i, void *addr)
{unsigned int tem = (unsigned int)i >> 5; /* Get the bitmap subscript */unsigned int *addri = (unsigned int *)addr + tem;unsigned int old = *addri;old = old | (1UL << ((unsigned int)i & 0x1f)); /* set the new map bit */*addri = old;
}
//获取指定位,看是否已经被分配
bool get_bit(int i)
{unsigned int *p = NULL;unsigned int mask;p = ((unsigned int *)bitmap) + (i >> 5); /* Gets the location in the bitmap */mask = 1 << (i & 0x1f); /* Gets the mask for the current bit int bitmap */if (!(~(*p) & mask)){return true;}return false;
}
  • tg_filelist是全局系统文件列表,统一管理系统fd,其中的关键结构体是 file,这才是内核对文件对象描述的实体,是本篇最重要的内容.
    #if CONFIG_NFILE_DESCRIPTORS > 0struct filelist tg_filelist; //全局统一管理系统文件句柄#endifstruct filelist{sem_t   fl_sem;               /* Manage access to the file list */struct file fl_files[CONFIG_NFILE_DESCRIPTORS];};struct file{unsigned int         f_magicnum;  /* file magic number */int                  f_oflags;    /* Open mode flags */struct Vnode         *f_vnode;    /* Driver interface */loff_t               f_pos;       /* File position */unsigned long        f_refcount;  /* reference count */char                 *f_path;     /* File fullpath */void                 *f_priv;     /* Per file driver private data */const char           *f_relpath;  /* realpath */struct page_mapping  *f_mapping;  /* mapping file to memory */void                 *f_dir;      /* DIR struct for iterate the directory if open a directory */const struct file_operations_vfs *ops;int fd;};
*   `f_magicnum`魔法数字,每种文件格式不同魔法数字不同,`gif`是`47 49 46 38`,`png`是`89 50 4e 47`
*   `f_oflags` 操作文件的权限模式,读/写/执行
*   `f_vnode` 对应的`vnode`
*   `f_pos` 记录操作文件的当前位置
*   `f_refcount` 文件被引用的次数,即文件被所有进程打开的次数.
*   `f_priv` 文件的私有数据
*   `f_relpath` 记录文件的真实路径
*   `f_mapping` 记录文件和内存的映射关系,这个在文件映射篇中有详细介绍.
*   `ops` 对文件内容的操作函数
*   `fd` 文件句柄编号,系统文件句柄是唯一的,一直到申请完为止,当`f_refcount`为0时,内核将回收`fd`.

open | creat | 申请文件句柄

通过文件路径名pathname获取文件句柄,鸿蒙实现过程如下

SysOpen //系统调用AllocProcessFd  //分配进程文件句柄do_open //向底层打开文件fp_open //vnode 层操作files_allocatefilep->ops->open(filep) //调用各文件系统的函数指针AssociateSystemFd //绑定系统文件句柄

建一个file对象,i即为分配到的系统文件句柄.

//创建系统文件对象及分配句柄
int files_allocate(struct Vnode *vnode_ptr, int oflags, off_t pos, void *priv, int minfd)//...while (i < CONFIG_NFILE_DESCRIPTORS)//系统描述符{p = ((unsigned int *)bitmap) + (i >> 5); /* Gets the location in the bitmap */mask = 1 << (i & 0x1f); /* Gets the mask for the current bit int bitmap */if ((~(*p) & mask))//该位可用于分配{set_bit(i, bitmap);//占用该位list->fl_files[i].f_oflags   = oflags;list->fl_files[i].f_pos      = pos;//偏移位list->fl_files[i].f_vnode    = vnode_ptr;//vnodelist->fl_files[i].f_priv     = priv;//私有数据list->fl_files[i].f_refcount = 1;	//引用数默认为1list->fl_files[i].f_mapping  = NULL;//暂无映射list->fl_files[i].f_dir      = NULL;//暂无目录list->fl_files[i].f_magicnum = files_magic_generate();//魔法数字process_files = OsCurrProcessGet()->files;//获取当前进程文件管理器return (int)i;}i++;}// ...
}

read | write

SysRead   //系统调用|读文件:从文件中读取nbytes长度的内容到buf中(用户空间)fd = GetAssociatedSystemFd(fd); //通过进程fd获取系统fdread(fd, buf, nbytes);  //调用系统fd层的读函数fs_getfilep(fd, &filep);  //通过系统fd获取file对象file_read(filep, buf, nbytes) //调用file层的读文件ret = (int)filep->ops->read(filep, (char *)buf, (size_t)nbytes);//调用具体文件系统的读操作

SysWrite   //系统调用|写文件:将buf中(用户空间)nbytes长度的内容写到文件中fd = GetAssociatedSystemFd(fd); //通过进程fd获取系统fdwrite(sysfd, buf, nbytes);  //调用系统fd层的写函数fs_getfilep(fd, &filep);  //通过系统fd获取file对象file_seek64file_write(filep, buf, nbytes);//调用file层的写文件ret = filep->ops->write(filep, (const char *)buf, nbytes);//调用具体文件系统的写操作

此处仅给出 file_write 的实现

ssize_t file_write(struct file *filep, const void *buf, size_t nbytes)
{int ret;int err;if (buf == NULL){err = EFAULT;goto errout;}/* Was this file opened for write access? */if ((((unsigned int)(filep->f_oflags)) & O_ACCMODE) == O_RDONLY){err = EACCES;goto errout;}/* Is a driver registered? Does it support the write method? */if (!filep->ops || !filep->ops->write){err = EBADF;goto errout;}/* Yes, then let the driver perform the write */ret = filep->ops->write(filep, (const char *)buf, nbytes);if (ret < 0){err = -ret;goto errout;}return ret;errout:set_errno(err);return VFS_ERROR;
}      

close

//关闭文件句柄
int SysClose(int fd)
{int ret;/* Process fd convert to system global fd */int sysfd = DisassociateProcessFd(fd);//先解除关联ret = close(sysfd);//关闭文件,个人认为应该先 close - > DisassociateProcessFd if (ret < 0) {//关闭失败时AssociateSystemFd(fd, sysfd);//继续关联return -get_errno();}FreeProcessFd(fd);//释放进程fdreturn ret;
}
  • 解除进程fd和系统fd的绑定关系
  • close时会有个判断,这个文件的引用数是否为0,只有为0才会真正的执行_files_close
    int files_close_internal(int fd, LosProcessCB *processCB){//...list->fl_files[fd].f_refcount--;if (list->fl_files[fd].f_refcount == 0){#ifdef LOSCFG_KERNEL_VMdec_mapping_nolock(filep->f_mapping);#endifret = _files_close(&list->fl_files[fd]);if (ret == OK){clear_bit(fd, bitmap);}}// ... }static int _files_close(struct file *filep){struct Vnode *vnode = filep->f_vnode;int ret = OK;/* Check if the struct file is open (i.e., assigned an vnode) */if (filep->f_oflags & O_DIRECTORY){ret = closedir(filep->f_dir);if (ret != OK){return ret;}}else{/* Close the file, driver, or mountpoint. */if (filep->ops && filep->ops->close){/* Perform the close operation */ret = filep->ops->close(filep);if (ret != OK){return ret;}}VnodeHold();vnode->useCount--;/* Block char device is removed when close */if (vnode->type == VNODE_TYPE_BCHR){ret = VnodeFree(vnode);if (ret < 0){PRINTK("Removing bchar device %s failed\n", filep->f_path);}}VnodeDrop();}/* Release the path of file */free(filep->f_path);/* Release the file descriptor */filep->f_magicnum = 0;filep->f_oflags   = 0;filep->f_pos      = 0;filep->f_path     = NULL;filep->f_priv     = NULL;filep->f_vnode    = NULL;filep->f_refcount = 0;filep->f_mapping  = NULL;filep->f_dir      = NULL;return ret;}    
  • 最后FreeProcessFd负责释放该文件在进程层面占用的资源

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

在这里插入图片描述

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN

图片

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com