BeeGFS
的客户端是由一个内核模块和两个系统服务组成的,这里我们主要分析内核模块。内核模块主要实现了一个 Linux
的文件系统,因此注册了一个文件系统类型。因为 BeeGFS
的目录树解析,是在父目录 DEntry
里找子目录或文件 DEntry
,逐级迭代完成的,因此在 Mount
文件系统时,需要从管理节点获取根元数据节点的 ID
,然后再向根元数据节点查询根目录的 DEntry
的信息,为后续的目录解析打下基础。
// fhgfs_client_module\source\program\Main.c
#define BEEGFS_LICENSE "GPL v2"
static int __init init_fhgfs_client(void)
{
#define fail_to(target, msg) \
do { \
printk_fhgfs(KERN_WARNING, msg "\n"); \
goto target; \
} while (0)
if (!beegfs_fault_inject_init() )
fail_to(fail_fault, "could not register fault-injection debugfs dentry");
if (!beegfs_native_init() )
fail_to(fail_native, "could not allocate emergency pools");
if (!FhgfsOpsCommKit_initEmergencyPools() )
fail_to(fail_commkitpools, "could not allocate emergency pools");
if (!SocketTk_initOnce() )
fail_to(fail_socket, "SocketTk initialization failed");
if (!FhgfsOps_initInodeCache() )
fail_to(fail_inode, "Inode cache initialization failed");
if (!RWPagesWork_initworkQueue() )
fail_to(fail_rwpages, "Page work queue registration failed");
if (!FhgfsOpsRemoting_initMsgBufCache() )
fail_to(fail_msgbuf, "Message cache initialization failed");
if (!FhgfsOpsPages_initPageListVecCache() )
fail_to(fail_pagelists, "PageVec cache initialization failed");
if (FhgfsOps_registerFilesystem() )
fail_to(fail_register, "File system registration failed");
ProcFs_createGeneralDir();
printk_fhgfs(KERN_INFO, "File system registered. Type: %s. Version: %s\n",
BEEGFS_MODULE_NAME_STR, App_getVersionStr() );
return 0;
fail_register:
FhgfsOpsPages_destroyPageListVecCache();
fail_pagelists:
FhgfsOpsRemoting_destroyMsgBufCache();
fail_msgbuf:
RWPagesWork_destroyWorkQueue();
fail_rwpages:
FhgfsOps_destroyInodeCache();
fail_inode:
SocketTk_uninitOnce();
fail_socket:
FhgfsOpsCommKit_releaseEmergencyPools();
fail_commkitpools:
beegfs_native_release();
fail_native:
beegfs_fault_inject_release();
fail_fault:
return -EPERM;
}
static void __exit exit_fhgfs_client(void)
{
ProcFs_removeGeneralDir();
BUG_ON(FhgfsOps_unregisterFilesystem() );
FhgfsOpsPages_destroyPageListVecCache();
FhgfsOpsRemoting_destroyMsgBufCache();
RWPagesWork_destroyWorkQueue();
FhgfsOps_destroyInodeCache();
SocketTk_uninitOnce();
FhgfsOpsCommKit_releaseEmergencyPools();
beegfs_native_release();
beegfs_fault_inject_release();
printk_fhgfs(KERN_INFO, "BeeGFS client unloaded.\n");
}
module_init(init_fhgfs_client)
module_exit(exit_fhgfs_client)
MODULE_LICENSE(BEEGFS_LICENSE);
MODULE_DESCRIPTION("BeeGFS parallel file system client (http://www.beegfs.com)");
MODULE_AUTHOR("Fraunhofer ITWM, CC-HPC");
BeeGFS
文件系统类型: // fhgfs_client_module\source\filesystem\FhgfsOpsSuper.c
static struct file_system_type fhgfs_fs_type =
{
.name = BEEGFS_MODULE_NAME_STR,
.owner = THIS_MODULE,
.kill_sb = FhgfsOps_killSB,
//.fs_flags = FS_BINARY_MOUNTDATA, // not required currently
#ifdef KERNEL_HAS_GET_SB_NODEV
.get_sb = FhgfsOps_getSB,
#else
.mount = FhgfsOps_mount, // basically the same thing as get_sb before
#endif
};
int FhgfsOps_registerFilesystem(void)
{
return register_filesystem(&fhgfs_fs_type);
}
Mount
文件系统时,间接调用 FhgfsOps_fillSuper
来填充文件系统超级块。 // fhgfs_client_module\source\filesystem\FhgfsOps_versions.c
#ifdef KERNEL_HAS_GET_SB_NODEV
int FhgfsOps_getSB(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
return get_sb_nodev(fs_type, flags, data, FhgfsOps_fillSuper, mnt);
}
#else
/* kernel 2.6.39 switched from get_sb() to mount(), which provides similar functionality from our point of view. */
struct dentry* FhgfsOps_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags, data, FhgfsOps_fillSuper);
}
#endif // LINUX_VERSION_CODE
inode
,此时 ID
只是简单的初始化为 0
,后面会更新成真正的 ID
: // fhgfs_client_module\source\filesystem\FhgfsOpsSuper.c
/**
* Fill the file system superblock (vfs object)
*/
int FhgfsOps_fillSuper(struct super_block* sb, void* rawMountOptions, int silent)
{
App* app = NULL;
Config* cfg = NULL;
struct inode* rootInode;
struct dentry* rootDentry;
struct kstat kstat;
EntryInfo entryInfo;
FhgfsIsizeHints iSizeHints;
// init per-mount app object
if(__FhgfsOps_constructFsInfo(sb, rawMountOptions) )
return -ECANCELED;
app = FhgfsOps_getApp(sb);
cfg = App_getConfig(app);
// set up super block data
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = BEEGFS_MAGIC;
sb->s_op = &fhgfs_super_ops;
sb->s_time_gran = 1000000000; // granularity of c/m/atime in ns
sb->s_flags |= MS_NODIRATIME;
if (Config_getSysXAttrsEnabled(cfg ) )
sb->s_xattr = fhgfs_xattr_handlers_noacl; // handle only user xattrs
#ifdef KERNEL_HAS_POSIX_GET_ACL
if (Config_getSysACLsEnabled(cfg) )
{
sb->s_xattr = fhgfs_xattr_handlers; // replace with acl-capable xattr handlers
sb->s_flags |= MS_POSIXACL;
}
#endif // KERNEL_HAS_POSIX_GET_ACL
/* MS_ACTIVE is rather important as it marks the super block being successfully initialized and
* allows the vfs to keep important inodes in the cache. However, it seems it is already
* initialized in vfs generic mount functions.
sb->s_flags |= MS_ACTIVE; // used in iput_final() */
// NFS kernel export is probably not worth the backport efforts for kernels before 2.6.29
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
sb->s_export_op = &fhgfs_export_ops;
#endif
#if defined(KERNEL_HAS_SB_BDI)
sb->s_bdi = FhgfsOps_getBdi(sb);
#endif
// init root inode
memset(&kstat, 0, sizeof(struct kstat) );
kstat.ino = BEEGFS_INODE_ROOT_INO;
kstat.mode = S_IFDIR | 0777; // allow access for everyone
kstat.atime = kstat.mtime = kstat.ctime = current_fs_time(sb);
kstat.uid = FhgfsCommon_getCurrentKernelUserID();
kstat.gid = FhgfsCommon_getCurrentKernelGroupID();
kstat.blksize = Config_getTuneInodeBlockSize(cfg);
kstat.nlink = 1;
// root entryInfo is always updated when someone asks for it (so we just set dummy values here)
EntryInfo_init(&entryInfo, NodeOrGroup_fromGroup(0), StringTk_strDup(""), StringTk_strDup(""),
StringTk_strDup(""), DirEntryType_DIRECTORY, 0);
rootInode = __FhgfsOps_newInode(sb, &kstat, 0, &entryInfo, &iSizeHints);
if(!rootInode || IS_ERR(rootInode) )
{
__FhgfsOps_destructFsInfo(sb);
return IS_ERR(rootInode) ? PTR_ERR(rootInode) : -ENOMEM;
}
rootDentry = d_make_root(rootInode);
if(!rootDentry)
{
__FhgfsOps_destructFsInfo(sb);
return -ENOMEM;
}
#ifdef KERNEL_HAS_S_D_OP
// linux 2.6.38 switched from individual per-dentry to defaul superblock d_ops.
/* note: Only set default dentry operations here, as we don't want those OPs set for the root
* dentry. In fact, setting as before would only slow down everything a bit, due to
* useless revalidation of our root dentry. */
sb->s_d_op = &fhgfs_dentry_ops;
#endif // KERNEL_HAS_S_D_OP
rootDentry->d_time = jiffies;
sb->s_root = rootDentry;
return 0;
}
// fhgfs_client_module\source\filesystem\FhgfsOpsSuper.c
/**
* Initialize sb->s_fs_info
*
* @return 0 on success, negative linux error code otherwise
*/
int __FhgfsOps_constructFsInfo(struct super_block* sb, void* rawMountOptions)
{
int res;
int appRes;
App* app;
Logger* log;
#if defined(KERNEL_HAS_SB_BDI) && !defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME)
struct backing_dev_info* bdi;
#endif
// use kzalloc to also zero the bdi
FhgfsSuperBlockInfo* sbInfo = kzalloc(sizeof(FhgfsSuperBlockInfo), GFP_KERNEL);
if (!sbInfo)
{
printk_fhgfs_debug(KERN_INFO, "Failed to allocate memory for FhgfsSuperBlockInfo");
sb->s_fs_info = NULL;
return -ENOMEM;
}
sb->s_fs_info = sbInfo;
appRes = __FhgfsOps_initApp(sb, rawMountOptions);
if(appRes)
{
printk_fhgfs_debug(KERN_INFO, "Failed to initialize App object");
res = -EINVAL;
goto outFreeSB;
}
app = FhgfsOps_getApp(sb);
log = App_getLogger(app);
IGNORE_UNUSED_VARIABLE(log);
#if defined(KERNEL_HAS_SB_BDI)
#if defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) && !defined(KERNEL_HAS_BDI_SETUP_AND_REGISTER)
{
static atomic_long_t bdi_seq = ATOMIC_LONG_INIT(0);
res = super_setup_bdi_name(sb, BEEGFS_MODULE_NAME_STR "-%ld",
atomic_long_inc_return(&bdi_seq));
}
#else
bdi = &sbInfo->bdi;
/* NOTE: The kernel expects a fully initialized bdi structure, so at a minimum it has to be
* allocated by kzalloc() or memset(bdi, 0, sizeof(*bdi)).
* we don't set the congest_* callbacks (like every other filesystem) because those are
* intended for dm and md.
*/
bdi->ra_pages = BEEGFS_DEFAULT_READAHEAD_PAGES;
#if defined(KERNEL_HAS_BDI_CAP_MAP_COPY)
res = bdi_setup_and_register(bdi, BEEGFS_MODULE_NAME_STR, BDI_CAP_MAP_COPY);
#else
res = bdi_setup_and_register(bdi, BEEGFS_MODULE_NAME_STR);
#endif
#endif
if (res)
{
Logger_logFormatted(log, 2, __func__, "Failed to init super-block (bdi) information: %d",
res);
__FhgfsOps_uninitApp(app);
goto outFreeSB;
}
#endif
// set root inode attribs to uninit'ed
FhgfsOps_setHasRootEntryInfo(sb, false);
FhgfsOps_setIsRootInited(sb, false);
printk_fhgfs(KERN_INFO, "BeeGFS mount ready.\n");
return 0; // all ok, res should be 0 here
outFreeSB:
kfree(sbInfo);
sb->s_fs_info = NULL;
return res;
}
// fhgfs_client_module\source\filesystem\FhgfsOpsSuper.c
/**
* Creates and initializes the per-mount application object.
*/
int __FhgfsOps_initApp(struct super_block* sb, char* rawMountOptions)
{
MountConfig* mountConfig;
bool parseRes;
App* app;
int appRes;
// create mountConfig (parse from mount options)
mountConfig = MountConfig_construct();
parseRes = MountConfig_parseFromRawOptions(mountConfig, rawMountOptions);
if(!parseRes)
{
MountConfig_destruct(mountConfig);
return APPCODE_INVALID_CONFIG;
}
//printk_fhgfs(KERN_INFO, "Initializing App...\n"); // debug in
app = FhgfsOps_getApp(sb);
App_init(app, mountConfig);
appRes = App_run(app);
if(appRes != APPCODE_NO_ERROR)
{ // error occurred => clean up
printk_fhgfs_debug(KERN_INFO, "Stopping App...\n");
App_stop(app);
printk_fhgfs_debug(KERN_INFO, "Cleaning up...\n");
App_uninit(app);
printk_fhgfs_debug(KERN_INFO, "App unitialized.\n");
return appRes;
}
ProcFs_createEntries(app);
return appRes;
}
// fhgfs_client_module\source\app\App.c
int App_run(App* this)
{
// init data objects & storage
if(!__App_initDataObjects(this, this->mountConfig) )
{
printk_fhgfs(KERN_WARNING,
"Configuration error: Initialization of common objects failed. "
"(Log file may provide additional information.)\n");
this->appResult = APPCODE_INVALID_CONFIG;
return this->appResult;
}
if(!__App_initInodeOperations(this) )
{
printk_fhgfs(KERN_WARNING, "Initialization of inode operations failed.");
this->appResult = APPCODE_INITIALIZATION_ERROR;
return this->appResult;
}
if(!__App_initStorage(this) )
{
printk_fhgfs(KERN_WARNING, "Configuration error: Initialization of storage failed\n");
this->appResult = APPCODE_INVALID_CONFIG;
return this->appResult;
}
// init components
if(!__App_initComponents(this) )
{
printk_fhgfs(KERN_WARNING, "Component initialization error. "
"(Log file may provide additional information.)\n");
this->appResult = APPCODE_INITIALIZATION_ERROR;
return this->appResult;
}
__App_logInfos(this);
// start components
__App_startComponents(this);
// Note: We wait some ms for the node downloads here because the kernel would like to
// check the properties of the root directory directly after mount.
InternodeSyncer_waitForMgmtInit(this->internodeSyncer, 1000);
if(!__App_mountServerCheck(this) )
{ // mount check failed => cancel mount
printk_fhgfs(KERN_WARNING, "Mount sanity check failed. Canceling mount. "
"(Log file may provide additional information. Check can be disabled with "
"sysMountSanityCheckMS=0 in the config file.)\n");
this->appResult = APPCODE_INITIALIZATION_ERROR;
return this->appResult;
}
// mark: mount succeeded if we got here!
return this->appResult;
}
inode
基本操作,以备后面新建 inode
时使用: // fhgfs_client_module\source\app\App.c
/**
* Initialized the inode_operations structs depending on what features have been enabled in
* the config.
*/
bool __App_initInodeOperations(App* this)
{
Config* cfg = App_getConfig(this);
this->fileInodeOps = os_kzalloc(sizeof(struct inode_operations) );
this->symlinkInodeOps = os_kzalloc(sizeof(struct inode_operations) );
this->dirInodeOps = os_kzalloc(sizeof(struct inode_operations) );
this->specialInodeOps = os_kzalloc(sizeof(struct inode_operations) );
if (!this->fileInodeOps || !this->symlinkInodeOps ||
!this->dirInodeOps || !this->specialInodeOps)
{
SAFE_KFREE(this->fileInodeOps);
SAFE_KFREE(this->symlinkInodeOps);
SAFE_KFREE(this->dirInodeOps);
SAFE_KFREE(this->specialInodeOps);
return false;
}
this->fileInodeOps->getattr = FhgfsOps_getattr;
this->fileInodeOps->permission = FhgfsOps_permission;
this->fileInodeOps->setattr = FhgfsOps_setattr;
#ifdef KERNEL_HAS_GENERIC_READLINK
this->symlinkInodeOps->readlink = generic_readlink; // default is fine for us currently
#endif
#ifdef KERNEL_HAS_GET_LINK
this->symlinkInodeOps->get_link = FhgfsOps_get_link;
#else
this->symlinkInodeOps->follow_link = FhgfsOps_follow_link;
this->symlinkInodeOps->put_link = FhgfsOps_put_link;
#endif
this->symlinkInodeOps->getattr = FhgfsOps_getattr;
this->symlinkInodeOps->permission = FhgfsOps_permission;
this->symlinkInodeOps->setattr = FhgfsOps_setattr;
#ifdef KERNEL_HAS_ATOMIC_OPEN
#ifdef BEEGFS_ENABLE_ATOMIC_OPEN
this->dirInodeOps->atomic_open = FhgfsOps_atomicOpen;
#endif // BEEGFS_ENABLE_ATOMIC_OPEN
#endif
this->dirInodeOps->lookup = FhgfsOps_lookupIntent;
this->dirInodeOps->create = FhgfsOps_createIntent;
this->dirInodeOps->link = FhgfsOps_link;
this->dirInodeOps->unlink = FhgfsOps_unlink;
this->dirInodeOps->mknod = FhgfsOps_mknod;
this->dirInodeOps->symlink = FhgfsOps_symlink;
this->dirInodeOps->mkdir = FhgfsOps_mkdir;
this->dirInodeOps->rmdir = FhgfsOps_rmdir;
this->dirInodeOps->rename = FhgfsOps_rename;
this->dirInodeOps->getattr = FhgfsOps_getattr;
this->dirInodeOps->permission = FhgfsOps_permission;
this->dirInodeOps->setattr = FhgfsOps_setattr;
this->specialInodeOps->setattr = FhgfsOps_setattr;
if (Config_getSysXAttrsEnabled(cfg) )
{
this->fileInodeOps->listxattr = FhgfsOps_listxattr;
this->dirInodeOps->listxattr = FhgfsOps_listxattr;
#ifdef KERNEL_HAS_GENERIC_GETXATTR
this->fileInodeOps->getxattr = generic_getxattr;
this->fileInodeOps->removexattr = FhgfsOps_removexattr;
this->fileInodeOps->setxattr = generic_setxattr;
this->dirInodeOps->getxattr = generic_getxattr;
this->dirInodeOps->removexattr = FhgfsOps_removexattr;
this->dirInodeOps->setxattr = generic_setxattr;
#endif
if (Config_getSysACLsEnabled(cfg) )
{
#ifdef KERNEL_HAS_POSIX_GET_ACL
this->fileInodeOps->get_acl = FhgfsOps_get_acl;
this->dirInodeOps->get_acl = FhgfsOps_get_acl;
// Note: symlinks don't have ACLs
#ifdef KERNEL_HAS_SET_ACL
this->fileInodeOps->set_acl = FhgfsOps_set_acl;
this->dirInodeOps->set_acl = FhgfsOps_set_acl;
#endif // LINUX_VERSION_CODE
#else
Logger_logErr(this->logger, "Init inode operations",
"ACLs activated in config, but not supported on this kernel version.");
return false;
#endif // KERNEL_HAS_POSIX_GET_ACL
}
}
return true;
}
由 FhgfsOps_fillSuper
函数初始化调用。
Inode
时,会调用此函数,根据父目录的 DEntry
信息(其中保存有父目录所在的元数据节点 ID
,以及目录 ID
),访问相应的元数据节点进行子目录或者文件的操作: // fhgfs_client_module\source\filesystem\FhgfsOpsInode.h
/**
* See __FhgfsOps_newInodeWithParentID for details. This is just a wrapper function.
*/
struct inode* __FhgfsOps_newInode(struct super_block* sb, struct kstat* kstat, dev_t dev,
EntryInfo* entryInfo, FhgfsIsizeHints* iSizeHints)
{
return __FhgfsOps_newInodeWithParentID(sb, kstat, dev, entryInfo, (NumNodeID){0}, iSizeHints);
}
/**
* Creates a new inode, inits it from the kstat, inits the ops (depending on the mode)
* and hashes it.
*
* Note: Make sure everything is set in the kstat _before_ you call this, because we hash
* the inode in here (so it can be found and accessed by others when this method returns).
* Note: Consider using the _instantiateInode()-wrapper instead of calling this directly for new
* files/dirs.
*
* @param kstat must have a valid .ino (inode number)
* @param dev set to 0 if not required (only used for special files)
* @param entryInfoPtr contained strings will just be moved to the new inode or free'd in case of an
* error (or cached inode), so don't access the given entryInfoPtr anymore after calling this.
* @param parentNodeID: usually 0, except for NFS export callers, which needs it to connect dentries
* with their parents. By default dentries are connected to their parents, so usually this
* is not required (nfs is an exception).
* @return NULL if not successful
*/
struct inode* __FhgfsOps_newInodeWithParentID(struct super_block* sb, struct kstat* kstat,
dev_t dev, EntryInfo* entryInfo, NumNodeID parentNodeID, FhgfsIsizeHints* iSizeHints)
{
App* app = FhgfsOps_getApp(sb);
Config* cfg = App_getConfig(app);
FhgfsInode* fhgfsInode;
FhgfsInodeComparisonInfo comparisonInfo =
{
.inodeHash = kstat->ino, // pre-set by caller
.entryID = entryInfo->entryID,
};
// check inode cache for an existing inode with this ID (and get it) or allocate a new one
struct inode* inode = iget5_locked(sb, kstat->ino,
__FhgfsOps_compareInodeID, __FhgfsOps_initNewInodeDummy, &comparisonInfo);
if(unlikely(!inode || IS_ERR(inode) ) )
goto cleanup_entryInfo; // allocation of new inode failed
fhgfsInode = BEEGFS_INODE(inode);
if( !(inode->i_state & I_NEW) )
{ // Found an existing inode, which is possibly actively used. We still need to update it.
FhgfsInode_entryInfoWriteLock(fhgfsInode); // LOCK EntryInfo
FhgfsInode_updateEntryInfoUnlocked(fhgfsInode, entryInfo);
FhgfsInode_entryInfoWriteUnlock(fhgfsInode); // UNLOCK EntryInfo
spin_lock(&inode->i_lock);
__FhgfsOps_applyStatDataToInodeUnlocked(kstat, iSizeHints, inode); // already locked
Time_setToNow(&fhgfsInode->dataCacheTime);
spin_unlock(&inode->i_lock);
goto outNoCleanUp; // we found a matching existing inode => no init needed
}
fhgfsInode->parentNodeID = parentNodeID;
/* note: new inodes are protected by the I_NEW flag from access by other threads until we
* call unlock_new_inode(). */
// init this fresh new inode...
// no one can access inode yet => unlocked
__FhgfsOps_applyStatDataToInodeUnlocked(kstat, iSizeHints, inode);
inode->i_ino = kstat->ino; // pre-set by caller
inode->i_flags |= S_NOATIME | S_NOCMTIME; // timestamps updated by server
mapping_set_gfp_mask(&inode->i_data, GFP_USER); // avoid highmem for page cache pages
// move values (no actual string copy)
fhgfsInode->entryInfo = *entryInfo;
switch (kstat->mode & S_IFMT)
{
case S_IFREG: // regular file
{
if(Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Native)
{
inode->i_fop = &fhgfs_file_native_ops;
inode->i_data.a_ops = &fhgfs_addrspace_native_ops;
}
else
if(Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Paged)
{ // with pagecache
inode->i_fop = &fhgfs_file_pagecache_ops;
inode->i_data.a_ops = &fhgfs_address_pagecache_ops;
}
else
{ // no pagecache (=> either none or buffered cache)
inode->i_fop = &fhgfs_file_buffered_ops;
inode->i_data.a_ops = &fhgfs_address_ops;
}
#ifdef KERNEL_HAS_ADDRESS_SPACE_BDI
inode->i_data.backing_dev_info = FhgfsOps_getBdi(sb);
#endif
inode->i_op = App_getFileInodeOps(app);
} break;
case S_IFDIR: // directory
{
inode->i_op = App_getDirInodeOps(app);
inode->i_fop = &fhgfs_dir_ops;
} break;
case S_IFLNK: // symlink
{
inode->i_op = App_getSymlinkInodeOps(app);
} break;
default: // pipes and other special files
{
inode->i_op = App_getSpecialInodeOps(app);
init_special_inode(inode, kstat->mode, dev);
} break;
}
unlock_new_inode(inode); // remove I_NEW flag, so the inode can be accessed by others
return inode;
// error occured
cleanup_entryInfo:
EntryInfo_uninit(entryInfo);
// found an existing inode
outNoCleanUp:
return inode;
}
inode
操作指针: // fhgfs_client_module\source\app\App.h
struct inode_operations* App_getFileInodeOps(App* this)
{
return this->fileInodeOps;
}
struct inode_operations* App_getSymlinkInodeOps(App* this)
{
return this->symlinkInodeOps;
}
struct inode_operations* App_getDirInodeOps(App* this)
{
return this->dirInodeOps;
}
struct inode_operations* App_getSpecialInodeOps(App* this)
{
return this->specialInodeOps;
}