Writing Filesystems - Module Glue Code

From Genunix

Jump to: navigation, search

The Solaris Express developer documentation (like all older Solaris releases) has a manual called Writing Device Drivers, and another one that is more example-oriented called Device Driver Tutorial. These two books and other associated documentation show how to create block/character device driver modules, and describes what functionality the DDI (Device Driver Interface) provides for the use of these drivers to facilitate device register / memory and hardware access. I won't reiterate here what's in there, but will start with a comparison of how the module interfaces differ between character/block device drivers and filesystem modules, and end this section with the actual code snippets from the fat_vfsops.c sourcefile which contain the module linkage.

character/block device drivers have ... filesystem drivers have ...
int _init(void);
int _fini(void);
int _info(void);
Same prototypes for a filesystem drivers
Character/block device drivers initialize the xxx(9e) entry points statically by providing a cb_ops and a dev_ops function table, which is referenced directly via the data structure struct modldrv which identifies loadable device drivers to the Solaris kernel's module framework. Filesystem drivers interface with the Solaris kernel's module framework using a different module descriptor structure, struct modlfs (lfs, loadable filesystem). There is no longer any static function table passed in for filesystems, but instead only a vfsdef_t structure. This has two purposes:
  1. Provide a hook to the actual filesystem initalization function
  2. pass a mount options table to the framework

Looks much different:

static struct cb_ops mydrv_cb_ops = {
	...
};

static struct dev_ops mydrv_dev_ops = {
	...
	mydrv_cb_ops;	/* dev_ops - (9e) vector */
	...
};

/* modldrv structure */
static struct modldrv md = {
	&mod_driverops,	/* Module type - a driver. */
	"dummy driver",	/* Module name. */
	&dummy_dev_ops
};

/* modlinkage structure */
static struct modlinkage ml = {
	MODREV_1,
	&md,
	NULL
};
static mntopt_t fatfs_mntopttbl[] = {
	...
};

static mntopts_t fatfs_mntopt_prototype = {
	sizeof (fatfs_mntopttbl) / sizeof (mntopt_t),
	fatfs_mntopttbl
};

static int fatfsinit(int, char *);

static vfsdef_t vfw = {
	VFSDEF_VERSION,
	"fatfs",
	fatfsinit,
	VSW_HASPROTO|VSW_CANREMOUNT|VSW_STATS,
	&fatfs_mntopt_prototype
};

static struct modlfs modlfs = {
	&mod_fsops, "politically correct fs", &vfw
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modlfs, NULL
};

So unlike character/block device drivers, filesystem drivers do not provide any static function tables. The only statically-created information that's passed to the framework via struct modlfs / vfsdef_t is the table of mount options that are proprietary to this filesystem type. Anything else (!), in particular the function vectors for per-instance (VFS_) and per-node (VN_) operations, are created by the initialization hook, fatfsinit() in the case shown above.

Why do this ? Simple - it allows for changes to the framework, like modifications/extensions of the function call tables - i.e., provide new, previously undefined VFS_* and VN_* entry points.

Next: Writing Filesystems - Mount Options
Personal tools