このコンテンツは,特集「ソースコードを読もう!」第1部「Linuxカーネルの探検に出発!」で,誌面では紹介しきれなかった解説を補足したものです。本誌を読んでいることを前提にしていますが,LinuxやC言語の表記についての基礎的な知識も必要です。また,本誌の内容と一部重なる部分もあります。あらかじめご了承ください。(編集部)
(2)VFSによるマウントのようすを理解する
本誌79ページのVFSによるファイル・システムのマウントについて,もう少し詳しく解説しています
(3)システム・コールWrite()を読む
システム・コールwrite( )のコードを読みます。
(4)Linuxカーネル探検おまけの知識
カーネル探検で役立つちょっとした情報です
Linuxのファイル・システムを理解するうえで,ポイントとなるデータ構造は三つある。
(1)file構造体
(2)inode構造体
(3)super_block構造体
である。ファイルに関する情報はfile構造体が,ファイル内部に関する情報はinode構造体が,そしてファイル・システムに関する情報はsuper_block構造体がそれぞれ持っている。各構造体の中身を見てみよう。
struct file {
struct file*f_next, **f_pprev;
struct dentry*f_dentry; // dentry はディスク・キャシュ
struct file_operations*f_op;// 操作ルーチンへのポインタ
mode_tf_mode;
loff_tf_pos;
unsigned int f_count, f_flags; // open時のフラグ
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
struct fown_structf_owner; // fileのオーナー
unsigned intf_uid, f_gid;
intf_error;
unsigned longf_version;
/* needed for tty driver, and maybe others */
void*private_data;
};
(注意)//は筆者のコメントである。
このうち注目したいのは,ファイルに関する操作ルーチンへのポインタを持つfile_operationsである。file_operations構造体を見てみよう(/usr/src/linux/include/linux/fs.h)。
struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};
openやらwriteやらファイル操作用の各関数ルーチンへのポインタがずらっと表記してあることがわかるだろう。例えば,ext2ファイル・システム(Linuxの標準ファイル・システム)の操作は下記のように定義されている(/usr/src/linux/fs/ext2/file.c)。write( )の操作はext2_file_write( ) がつかさどるわけだ。
/*
* We have mostly NULL's here: the current defaults are ok for
* the ext2 filesystem.
*/
static struct file_operations ext2_file_operations = {
ext2_file_lseek,/* lseek */
generic_file_read,/* read */
ext2_file_write,/* write */
NULL,/* readdir - bad */
NULL,/* poll - default */
ext2_ioctl,/* ioctl */
generic_file_mmap,/* mmap */
#if BITS_PER_LONG == 64
NULL,/* no special open is needed */
#else
ext2_open_file,
#endif
NULL,/* flush */
ext2_release_file,/* release */
ext2_sync_file,/* fsync */
NULL,/* fasync */
NULL,/* check_media_change */
NULL/* revalidate */
};
では,file構造体のf_opはどのように利用されているだろうか。fsディレクトリ内の利用をgrepで検索してみよう。
[yoshioka@localhost fs]$ grep "¥->f_op" `find . -name "*.[chS]" -print`
... // 略
./devices.c:filp->f_op = get_blkfops(MAJOR(inode->i_rdev));
./devices.c:if (filp->f_op != NULL){
./devices.c:if (filp->f_op->open != NULL)
./devices.c:ret = filp->f_op->open(inode,filp);
./devices.c:filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
...
./fifo.c:filp->f_op = &connecting_fifo_fops;
./fifo.c:filp->f_op = &read_fifo_fops;
./fifo.c:filp->f_op = &write_fifo_fops;
./fifo.c:filp->f_op = &rdwr_fifo_fops;
./file_table.c:filp->f_op = dentry->d_inode->i_op->default_file_ops;
./file_table.c:if (filp->f_op->open)
./file_table.c:return filp->f_op->open(dentry->d_inode, filp);
...
./read_write.c:if (file->f_op && file->f_op->llseek)
./read_write.c:fn = file->f_op->llseek;
./read_write.c:if (!file->f_op || !(read = file->f_op->read))
./read_write.c:if (!file->f_op || !(write = file->f_op->write))
代入文(=)の左側にいるときと,右にいるときの意味の差を考えよう。また,if (!file->f_op || !file->f_op-> ...) などのイディオムもよく見かける。データ構造の利用のされかたを横断的にながめると,その機能が浮びあがってくる。そこで,f_opの使われ方として上記のllseek()の実装を見てみよう(/usr/src/linux/fs/read_write.c)。
static inline loff_t llseek(struct file *file, loff_t offset, int origin)
{
loff_t (*fn)(struct file *, loff_t, int);
fn = default_llseek;// デフォルトのllseek関数(1)
if (file->>f_op && file->f_op->llseek)
fn = file->f_op->llseek;
//もし,ファイル・システムが自前のllseek関数をもっていたら
//それを使う。
return fn(file, offset, origin);
}
fnにdefault_llseekというアドレスを代入している。default_llseek() はfs/read_write.c に定義されている(1)。もし,f_opが定義されていて,かつf_op->llseekが定義されていたら,先に代入したデフォルトのルーチンではなく,file->f_op->llseekルーチンを利用してそれをcallすることがわかる。
/*
* We have mostly NULL's here: the current defaults are ok for
* the ext2 filesystem.
*/
static struct file_operations ext2_file_operations = {
ext2_file_lseek,/* lseek */
generic_file_read,/* read */
ext2_file_write,/* write */
NULL,/* readdir - bad */
NULL,/* poll - default */
ext2_ioctl,/* ioctl */
generic_file_mmap,/* mmap */
#if BITS_PER_LONG == 64
NULL,/* no special open is needed */
#else
ext2_open_file,
#endif
NULL,/* flush */
ext2_release_file,/* release */
ext2_sync_file,/* fsync */
NULL,/* fasync */
NULL,/* check_media_change */
NULL/* revalidate */
};
このように,ext2_file_lseek()がllseekの実体になる
UNIX/Linuxでは,ファイルは階層的に保持され,ルート・ディレクトリ(/で表現する)からツリー構造になっている。このとき,inodeと呼ばれるファイルの情報を格納したデータ構造が利用される(/usr/src/linux/include/linux/fs.h)。
struct inode {
struct list_headi_hash; // 双方向リンク
struct list_headi_list;
struct list_headi_dentry;
unsigned long i_ino; // inode番号
unsigned int i_count; // 参照数
kdev_t i_dev; // ファイル装置番号
umode_t i_mode; // fileの種類,アクセス権
nlink_t i_nlink; // link数
uid_t i_uid; // ユーザーid
gid_t i_gid; // グループid
kdev_t i_rdev; // 装置
off_t i_size; // ファイル・サイズ
time_t i_atime; // アクセスした時刻
time_t i_mtime; // 変更した時刻
time_t i_ctime; // 生成した時刻
unsigned long i_blksize; // ブロック・サイズ
unsigned long i_blocks; // ブロック数
unsigned long i_version;
unsigned long i_nrpages;
struct semaphorei_sem;
struct semaphorei_atomic_write;
struct inode_operations*i_op; // 後述
struct super_block*i_sb; // super_blockへ
// のポインタ
struct wait_queue*i_wait; // 待ち行列
struct file_lock*i_flock; // ファイル・ロック
struct vm_area_struct*i_mmap; // メモリー領域
struct page *i_pages;
struct dquot *i_dquot[MAXQUOTAS];
unsigned long i_state;
unsigned int i_flags;
unsigned char i_pipe; // pipe
unsigned char i_sock; // socket
int i_writecount; // 書き込み許可数
unsigned int i_attr_flags;
__u32 i_generation;
union { // 各ファイル・システムに依存した情報
struct pipe_inode_infopipe_i;
struct minix_inode_infominix_i;
struct ext2_inode_infoext2_i;
struct hpfs_inode_infohpfs_i;
struct ntfs_inode_info ntfs_i;
struct msdos_inode_infomsdos_i;
struct umsdos_inode_infoumsdos_i;
struct iso_inode_infoisofs_i;
struct nfs_inode_infonfs_i;
struct sysv_inode_infosysv_i;
struct affs_inode_infoaffs_i;
struct ufs_inode_infoufs_i;
struct romfs_inode_inforomfs_i;
struct coda_inode_infocoda_i;
struct smb_inode_infosmbfs_i;
struct hfs_inode_infohfs_i;
struct adfs_inode_infoadfs_i;
struct qnx4_inode_infoqnx4_i;
struct socketsocket_i;
void*generic_ip;
} u; // inode 情報
};
例えば,Linuxの標準的なファイル・システムであるext2のinode_infoは,下記のような情報を持つ。
/*
* second extended file system inode data in memory
*/
struct ext2_inode_info {
__u32i_data[15];
__u32i_flags;
__u32i_faddr;
__u8i_frag_no;
__u8i_frag_size;
__u16i_osync;
__u32i_file_acl;
__u32i_dir_acl;
__u32i_dtime;
__u32i_version;
__u32i_block_group;
__u32i_next_alloc_block;
__u32i_next_alloc_goal;
__u32i_prealloc_block;
__u32i_prealloc_count;
__u32i_high_size;
inti_new_inode:1;/* Is a freshly allocated inode */
};
i_data[ ]の最初の12個(EXT2_NDIR_BLOCKS)は直接データブロックへのポインタを持つ。データブロックのサイズが4KBなら,最初の12個のi_data[]によって48KBまでのファイルが示せる。i_data[12]は1ページのデータブロックを指し,そのデータブロックは,4KB/4byte=1Kエントリのデータブロックを指す。したがって4MB。i_data[13]は2重間接ブロックを持ち,i_data[14]は3重間接ブロックを持つ。
[yoshioka@localhost fs]$ stat / File: "/" Size: 1024 Filetype: Directory Mode: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Device: 3,5 Inode: 2 Links: 16 Access: Mon Jun 21 11:17:31 1999(00000.09:31:49) Modify: Sun May 23 13:09:01 1999(00029.07:40:19) Change: Sun May 23 13:09:01 1999(00029.07:40:19)inodeは,デバイス番号(3,5),inode番号(2)などを持つ。ここで3は,デバイスのmajor番号と呼ばれるもので,3はblock型デバイスならIDEである。5はminor番号とよばれ,hda5を表す。Documentation/device.txtを参照してほしい。
super_block(super_block)はファイル・システムの情報を持つ(/usr/src/linux/include/linux/fs.h)。
struct super_block {
struct list_heads_list;/* Keep this first */
kdev_ts_dev; // デバイスファイルの場合
unsigned longs_blocksize; // ブロック・サイズ
unsigned chars_blocksize_bits; // ブロック・サイズ(bit)
unsigned chars_lock;
unsigned chars_rd_only;
unsigned chars_dirt; // 変更のフラグ
struct file_system_type*s_type; // ファイル型
struct super_operations*s_op; // 操作ルーチン
struct dquot_operations*dq_op; // disk quota操作
unsigned longs_flags; // mount時のフラグ
unsigned longs_magic; // マジックナンバー
unsigned longs_time;
struct dentry*s_root;
struct wait_queue*s_wait;
struct inode*s_ibasket;
short ints_ibasket_count;
short ints_ibasket_max;
struct list_heads_dirty;/* dirty inodes */
union { // 各ファイル・システム固有の情報を以下にもつ
struct minix_sb_infominix_sb;
struct ext2_sb_infoext2_sb;
struct hpfs_sb_infohpfs_sb;
struct ntfs_sb_info ntfs_sb;
struct msdos_sb_infomsdos_sb;
struct isofs_sb_infoisofs_sb;
struct nfs_sb_infonfs_sb;
struct sysv_sb_infosysv_sb;
struct affs_sb_infoaffs_sb;
struct ufs_sb_infoufs_sb;
struct romfs_sb_inforomfs_sb;
struct smb_sb_infosmbfs_sb;
struct hfs_sb_infohfs_sb;
struct adfs_sb_infoadfs_sb;
struct qnx4_sb_infoqnx4_sb;
void*generic_sbp;
} u;
};
ファイル・システムを利用するためには,そのファイル・システムをマウントする必要がある。マウントされたファイル・システムは,super_blockによって表現される。super_blockは,ファイル・システム全体の情報を持つ。各ファイル・システム固有の情報については, union { ... } u; の部分で定義している。s_flagsがmount時のフラグを持つのである。super_blockへの操作を見てみよう(/usr/src/linux/include/linux/fs.h)。
struct super_operations {
void (*read_inode) (struct inode *);
void (*write_inode) (struct inode *);
void (*put_inode) (struct inode *);
void (*delete_inode) (struct inode *);
int (*notify_change) (struct dentry *, struct iattr *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *, int);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);
};
このデータ構造は,super_blockへのすべての操作(メソッド)を与えている。関数群をデータ構造のなかに閉じ込めたため,関数の呼び出し側は,ファイル・システムの実装の差を知る必要がなくなった。まさしくここがVFSのメリットといえるところだろう。例えば,ext2ファイル・システムの操作はext2_sopsという構造体で与えられている(/usr/src/linux/fs/ext2/super.c)。
static struct super_operations ext2_sops = {
ext2_read_inode,
ext2_write_inode,
ext2_put_inode,
ext2_delete_inode,
NULL,
ext2_put_super,
ext2_write_super,
ext2_statfs,
ext2_remount
};
これを,
sb->s_op = &ext2_sops;
...
if (sb->s_op && sb->s_op->write_super)
sb->s_op->write_super(sb);
みたいな感じで呼び出す。続いて,inodeへの操作を見てみよう(/usr/src/linux/include/linux/fs.h)。
struct inode_operations {
struct file_operations * default_file_ops;
int (*create) (struct inode *,struct dentry *,int);
int (*lookup) (struct inode *,struct dentry *);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,int,int);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*readlink) (struct dentry *, char *,int);
struct dentry * (*follow_link) (struct dentry *, struct dentry *, unsigned int);
int (*readpage) (struct file *, struct page *);
int (*writepage) (struct file *, struct page *);
int (*bmap) (struct inode *,int);
void (*truncate) (struct inode *);
int (*permission) (struct inode *, int);
int (*smap) (struct inode *,int);
int (*updatepage) (struct file *, struct page *, unsigned long, unsigned int, int);
int (*revalidate) (struct dentry *);
};
inode構造体も,super_block同様に,inode_operations という操作の関数群へのポインタを持つ。各ルーチンの意味はほとんどあきらかだと思う。例えば,
struct inode * inode
inode->i_op = &xxxx // 各ファイル・システムの操作ルーチン
...
inode->i_op->create(...)
みたいな感じになる。ext2ファイル・システムの関数がどのようには定義されているか見てみよう(/usr/src/linux/fs/ext2/dir.c)。
/*
* directories can handle most operations...
*/
struct inode_operations ext2_dir_inode_operations = {
&ext2_dir_operations,/* default directory file-ops */
ext2_create,/* create */
ext2_lookup,/* lookup */
ext2_link,/* link */
ext2_unlink,/* unlink */
ext2_symlink,/* symlink */
ext2_mkdir,/* mkdir */
ext2_rmdir,/* rmdir */
ext2_mknod,/* mknod */
ext2_rename,/* rename */
NULL,/* readlink */
NULL,/* follow_link */
NULL,/* readpage */
NULL,/* writepage */
NULL,/* bmap */
NULL,/* truncate */
ext2_permission,/* permission */
NULL/* smap */
};
inode_operations/file_operations/super_operationsという構造体がそれぞれのデータ構造(inode/file/super block)への操作関数のポインタを持つのである。例えば,ext2_read_inode()というルーチンでは
inode->i_op = NULL;
if (inode->i_ino == EXT2_ACL_IDX_INO ||
inode->i_ino == EXT2_ACL_DATA_INO)
/* Nothing to do */ ;
else if (S_ISREG(inode->i_mode))
inode->i_op = &ext2_file_inode_operations;
else if (S_ISDIR(inode->i_mode))
inode->i_op = &ext2_dir_inode_operations;
else if (S_ISLNK(inode->i_mode))
inode->i_op = &ext2_symlink_inode_operations;
else if (S_ISCHR(inode->i_mode))
inode->i_op = &chrdev_inode_operations;
else if (S_ISBLK(inode->i_mode))
inode->i_op = &blkdev_inode_operations;
else if (S_ISFIFO(inode->i_mode))
init_fifo(inode);
というような感じで,i_opを初期化している。モードごとに操作ルーチンを変えているのである。同様なテクニックがいたるところで使われている。ファイル・システムに関するドキュメントはDocumentation/filesystems ディレクトリにあるので参考にしてほしい。
UNIX/Linuxのファイル・システムの場合,いわゆるルートディレクトリ"/" からファイル・システムが構成されているから,初期化のどこかの段階で,ファイル・システムのmount(マウント)が行われているはずである。その現場を見てみよう。fs ディレクトリにいるとする。
[yoshioka@localhost linux-2.2.5]$ cd fs
[yoshioka@localhost fs]$ pwd
/usr/src/linux-2.2.5/fs
[yoshioka@localhost fs]$ grep -n '"/"' `find . -name "*.[chS]" -print`
./affs/symlink.c:69:pf = inode->i_sb->u.affs_sb.s_prefix ? inode->i_sb->u.affs_sb.s_prefix : "/";
./affs/symlink.c:130:pf = inode->i_sb->u.affs_sb.s_prefix ? inode->i_sb->u.affs_sb.s_prefix : "/";
./dcache.c:550:res = d_alloc(NULL, &(const struct qstr) { "/", 1, 0 });
./isofs/rock.c:472: strcat(rpnt,"/");
./isofs/rock.c:487: && ((oldslp->flags & 1) == 0) ) strcat(rpnt,"/");
./isofs/rock.c:495: && ((oldslp->flags & 1) == 0) ) strcat(rpnt,"/");
./proc/proc_devtree.c:217:root = find_path_device("/");
./super.c:1133:vfsmnt = add_vfsmnt(sb, "/dev/root", "/");
./super.c:1210:vfsmnt = add_vfsmnt(sb, "/dev/root", "/");
いきなり"/"でgrepである。力技とよんでもいい。super.c:1133行ないしsuper.c:1210行あたりがあやしい。さっそくそのファイルを見る(/usr/src/linux/fs/super.c)。
void __init mount_root(void)
{
// 略
sb = read_super(ROOT_DEV,fs_type->name,root_mountflags,NULL,1);
if (sb) {
sb->s_flags = root_mountflags;
current->fs->root = dget(sb->s_root);
current->fs->pwd = dget(sb->s_root);
printk ("VFS: Mounted root (%s filesystem)%s.¥n",
fs_type->name,
(sb->s_flags & MS_RDONLY) ? " readonly" : "");
vfsmnt = add_vfsmnt(sb, "/dev/root", "/");
if (vfsmnt)
return;
panic("VFS: add_vfsmnt failed for root fs");
}
}
//略
というのが発見できる。printk()でなにやらメッセージも出している。dmesgでブート時のメッセージを確認してみよう。rootという文字列をgrepする。
[yoshioka@localhost fs]$ dmesg|grep root Linux version 2.2.5-15 (root@porky.devel.redhat.com) (gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)) #1 Mon Apr 19 22:21:09 EDT 1999 VFS: Mounted root (ext2 filesystem) readonly.ビンゴである。VFSはext2 filesystemをreadonlyでmount(マウント)している。そうすると,次の疑問は,mount_root()はどこからよばれているのか。それを追及したくなるのが人情である。
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)
{
// 略
/* Launch bdflush from here, instead of the old syscall way. */
kernel_thread(bdflush, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
/* Start the background pageout daemon. */
kswapd_setup();
kernel_thread(kpiod, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
kernel_thread(kswapd, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
// 各種デーモンの起動
#ifdef CONFIG_BLK_DEV_INITRD
real_root_dev = ROOT_DEV;
real_root_mountflags = root_mountflags;
if (initrd_start && mount_initrd) root_mountflags &= ‾MS_RDONLY;
else mount_initrd =0;
#endif
//中略
/* .. filesystems .. */
filesystem_setup();
/* Mount the root filesystem.. */
mount_root();
//略
}
mountする直前で,filesystem_setupといういかにもファイル・システムの初期化をしていそうなルーチンを起動している。その直前には,root_mountflags という文字が見えるが,私たちは,ブート時のメッセージから,ルートは readonly でマウントされたということを知っている。また,do_basic_setup() ではファイル・システムだけでなく,各種デーモンなども起動しているようである。
私たちはカーネルの初期化を逆流している。filesystem_setup()もちょっと見てみよう(/usr/src/linux/fs/filesystem.c)。
void __init filesystem_setup(void)
{
#ifdef CONFIG_EXT2_FS
init_ext2_fs();
#endif
#ifdef CONFIG_MINIX_FS
init_minix_fs();
#endif
//略
}
#ifdef ... #endif で各種コンフィグレーションにしたがった設定ルーチンを起動している。各種コンフィグレーションによって,CONFIG_EXT2_FS等が設定されているのであろう。そこで,init_ext2_fs()を見る。
__initfunc(int init_ext2_fs(void))
{
return register_filesystem(&ext2_fs_type);
}
いよいよファイル・システムの登録の現場らしい。register_filesystemを見てみる。
int register_filesystem(struct file_system_type * fs)
{
struct file_system_type ** tmp;
if (!fs)
return -EINVAL;
if (fs->next)
return -EBUSY;
tmp = &file_systems; // file_systems がリストの先頭
while (*tmp) { // file_system_typeのリストを先頭より検索
if (strcmp((*tmp)->name, fs->name) == 0)
return -EBUSY; // すでに登録されていたらエラー
tmp = &(*tmp)->next; //次の要素を探す
}
*tmp = fs; // リストの最後に追加する
return 0;
}
file_system_typeが気になる。
struct file_system_type {
const char *name;
int fs_flags;
struct super_block *(*read_super) (struct super_block *, void *, int);
struct file_system_type * next;
};
file_system_type という構造体はfile_systemsを先頭に持つリスト構造である。登録したファイル・システムを要素として持つのである。各ファイル・システムはその名前,フラグ,super_blockを読むルーチン(read_super())のアドレスをその要素として持つ。file_system_type でgrepすればどのようなファイル・システムがカーネル中に定義されているかがわかる。
static struct file_system_type ext2_fs_type = {
"ext2",
FS_REQUIRES_DEV /* | FS_IBASKET */,/* ibaskets have unresolved bugs */
ext2_read_super,
NULL
};
ずばり,ext2の文字が見える。というわけで
void __init mount_root(void)
{
struct file_system_type * fs_type;
struct super_block * sb;
struct vfsmount *vfsmnt;
struct inode * d_inode = NULL;
struct file filp;
int retval;
// 略
memset(&filp, 0, sizeof(filp));
d_inode = get_empty_inode();
//下記のようなinodeを作る
//inode->i_sb = NULL;
//inode->i_dev = 0;
//inode->i_ino = ++last_ino;
//inode->i_flags = 0;
//inode->i_count = 1;
//inode->i_state = 0;
d_inode->i_rdev = ROOT_DEV;
filp.f_dentry = NULL;
if ( root_mountflags & MS_RDONLY)
filp.f_mode = 1; /* read only */
else
filp.f_mode = 3; /* read write */
retval = blkdev_open(d_inode, &filp);
// block deviceをopen
if (retval == -EROFS) {
root_mountflags |= MS_RDONLY;
filp.f_mode = 1;
retval = blkdev_open(d_inode, &filp);
}
iput(d_inode);
if (retval)
/*
* Allow the user to distinguish between failed open
* and bad superblock on root device.
*/
printk("VFS: Cannot open root device %s¥n",
kdevname(ROOT_DEV));
else for (fs_type = file_systems ; fs_type ; fs_type = fs_type->next) {
if (!(fs_type->fs_flags & FS_REQUIRES_DEV))
continue;
sb = read_super(ROOT_DEV,fs_type->name,root_mountflags,NULL,1);
// 登録されているファイル・システムごとにsuper_blockを読みこみ
// マウントする
if (sb) {
sb->s_flags = root_mountflags;
current->fs->root = dget(sb->s_root);
current->fs->pwd = dget(sb->s_root);
printk ("VFS: Mounted root (%s filesystem)%s.¥n",
fs_type->name,
(sb->s_flags & MS_RDONLY) ? " readonly" : "");
// マウントできたファイル・システムを表示する
vfsmnt = add_vfsmnt(sb, "/dev/root", "/");
if (vfsmnt)
return;
panic("VFS: add_vfsmnt failed for root fs");
}
}
panic("VFS: Unable to mount root fs on %s",
kdevname(ROOT_DEV));
}
read_super() はsuper_blockを作成し,ファイル・システム固有のread_super()を呼びだす。ext2ファイル・システムの場合だと,ext2_read_super() である。これは先のext2_fs_typeで定義されている。
super_blocksはリストであって,各ファイル・システムのsuper_block構造体を要素に持つ。get_empty_super() で新規にsuper_blockを作るが,その最大個数はmax_super_blocksという変数に保持されていて,現在のsuper_blockの数すなわちマウントしているファイル・システムの数はnr_super_blocksである。
static struct super_block * read_super(kdev_t dev,const char *name,int flags,
void *data, int silent)
{
struct super_block * s;
struct file_system_type *type;
if (!dev)
goto out_null;
check_disk_change(dev);
s = get_super(dev); // deviceのsuper_blockを得る
if (s)
goto out;
type = get_fs_type(name); // ファイル・システムのタイプを得る
if (!type) {
printk("VFS: on device %s: get_fs_type(%s) failed¥n",
kdevname(dev), name);
goto out;
}
s = get_empty_super(); // 新規にsuper_blockを作成する
if (!s)
goto out;
s->s_dev = dev; // deviceを設定
s->s_flags = flags; // mount のflagsを設定
s->s_dirt = 0; // 変更されていない
/* N.B. Should lock superblock now ... */
if (!type->read_super(s, data, silent))
// super_blockを読む
goto out_fail;
s->s_dev = dev; /* N.B. why do this again?? */
s->s_rd_only = 0;
s->s_type = type;
out:
return s;
/* N.B. s_dev should be cleared in type->read_super */
out_fail:
s->s_dev = 0;
out_null:
s = NULL;
goto out;
}
先に見たように,関数のポインタを利用したファイル・システム固有の関数の呼びだし(type->read_super)などは,字面の上では,type->read_superが示している ext2_read_superというのが表れていないので,そのコントロール・フローを追うのは難しい。
システム・コールwrite()の実装のようすを見てみよう。
asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
{
ssize_t ret;
struct file * file;
struct inode * inode;
ssize_t (*write)(struct file *, const char *, size_t, loff_t *);
lock_kernel(); // ここではふれない
ret = -EBADF;
// EBADFが何を示すかは,man 2 writeで調べよう
file = fget(fd); // fd (file descriptor) から file 構造をえる
if (!file)
goto bad_file;
if (!(file->f_mode & FMODE_WRITE))
goto out;
// 書き込みモードでオープンされていなければ終了
inode = file->f_dentry->d_inode;
ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file,
file->f_pos, count);
if (ret)
goto out;
ret = -EINVAL;
if (!file->f_op || !(write = file->f_op->write))
goto out;
down(&inode->i_sem); //セマフォの獲得
ret = write(file, buf, count, &file->f_pos);
// write は file->f_op->write が指すルーチンである
// 各ファイル・システムごとのwrite()の実装を指している
// ext2 file systemなら ext2_file_write()
up(&inode->i_sem);
out:
fput(file);
bad_file:
unlock_kernel();
return ret;
}
さきに見た,イディオムがここでも利用されている。
if (!file->f_op || !(write = file->f_op->write)) goto out;fget()の実装は単純だ。
/*
* Check whether the specified fd has an open file.
*/
extern inline struct file * fcheck(unsigned int fd)
{
struct file * file = NULL;
if (fd < current->files->max_fds)
file = current->files->fd[fd];
return file;
}
extern inline struct file * fget(unsigned int fd)
{
struct file * file = fcheck(fd);
if (file)
file->f_count++;
return file;
}
current は現在のプロセスを指すグローバル変数である。現在のプロセスのファイル・ディスクリプタのテーブルから,file構造体を得ている。ext2ファイル・システムでのwrite()の実装は下記の通り。
static ssize_t ext2_file_write (struct file * filp, const char * buf,
size_t count, loff_t *ppos)
{
struct inode * inode = filp->f_dentry->d_inode;
off_t pos;
long block;
int offset;
int written, c;
struct buffer_head * bh, *bufferlist[NBUF];
struct super_block * sb;
int err;
int i,buffercount,write_error;
/* POSIX: mtime/ctime may not change for 0 count */
if (!count)
return 0;
write_error = buffercount = 0;
if (!inode) {
printk("ext2_file_write: inode = NULL¥n");
return -EINVAL;
}
sb = inode->i_sb;
if (sb->s_flags & MS_RDONLY)
/*
* This fs has been automatically remounted ro because of errors
*/
return -ENOSPC;
if (!S_ISREG(inode->i_mode)) {
ext2_warning (sb, "ext2_file_write", "mode = %07o",
inode->i_mode);
return -EINVAL;
}
remove_suid(inode);
if (filp->f_flags & O_APPEND)
pos = inode->i_size;
else {
pos = *ppos;
if (pos != *ppos)
return -EINVAL;
#if BITS_PER_LONG >= 64
if (pos > ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(sb)])
return -EINVAL;
#endif
}
/* Check for overflow.. */
#if BITS_PER_LONG < 64
if (pos > (__u32) (pos + count)) {
count = ‾pos; /* == 0xFFFFFFFF - pos */
if (!count)
return -EFBIG;
}
#else
{
off_t max = ext2_max_sizes[EXT2_BLOCK_SIZE_BITS(sb)];
if (pos + count > max) {
count = max - pos;
if (!count)
return -EFBIG;
}
if (((pos + count) >> 32) &&
!(sb->u.ext2_sb.s_es->s_feature_ro_compat &
cpu_to_le32(EXT2_FEATURE_RO_COMPAT_LARGE_FILE))) {
/* If this is the first large file created, add a flag
to the superblock */
sb->u.ext2_sb.s_es->s_feature_ro_compat |=
cpu_to_le32(EXT2_FEATURE_RO_COMPAT_LARGE_FILE);
mark_buffer_dirty(sb->u.ext2_sb.s_sbh, 1);
}
}
#endif
/*
* If a file has been opened in synchronous mode, we have to ensure
* that meta-data will also be written synchronously. Thus, we
* set the i_osync field. This field is tested by the allocation
* routines.
*/
if (filp->f_flags & O_SYNC)
inode->u.ext2_i.i_osync++;
block = pos >> EXT2_BLOCK_SIZE_BITS(sb);
offset = pos & (sb->s_blocksize - 1);
c = sb->s_blocksize - offset;
written = 0;
do {
bh = ext2_getblk (inode, block, 1, &err);
if (!bh) {
if (!written)
written = err;
break;
}
if (c > count)
c = count;
if (c != sb->s_blocksize && !buffer_uptodate(bh)) {
ll_rw_block (READ, 1, &bh);
wait_on_buffer (bh);
if (!buffer_uptodate(bh)) {
brelse (bh);
if (!written)
written = -EIO;
break;
}
}
c -= copy_from_user (bh->b_data + offset, buf, c);
if (!c) {
brelse(bh);
if (!written)
written = -EFAULT;
break;
}
update_vm_cache(inode, pos, bh->b_data + offset, c);
pos += c;
written += c;
buf += c;
count -= c;
mark_buffer_uptodate(bh, 1);
mark_buffer_dirty(bh, 0);
if (filp->f_flags & O_SYNC)
bufferlist[buffercount++] = bh;
else
brelse(bh);
if (buffercount == NBUF){
ll_rw_block(WRITE, buffercount, bufferlist);
for(i=0; i<buffercount; i++){
wait_on_buffer(bufferlist[i]);
if (!buffer_uptodate(bufferlist[i]))
write_error=1;
brelse(bufferlist[i]);
}
buffercount=0;
}
if(write_error)
break;
block++;
offset = 0;
c = sb->s_blocksize;
} while (count);
if ( buffercount ){
ll_rw_block(WRITE, buffercount, bufferlist);
for(i=0; i<buffercount; i++){
wait_on_buffer(bufferlist[i]);
if (!buffer_uptodate(bufferlist[i]))
write_error=1;
brelse(bufferlist[i]);
}
}
if (pos > inode->i_size)
inode->i_size = pos;
if (filp->f_flags & O_SYNC)
inode->u.ext2_i.i_osync--;
inode->i_ctime = inode->i_mtime = CURRENT_TIME;
*ppos = pos;
mark_inode_dirty(inode);
return written;
}
ちょっと大物ではあるが,各自ソースを追ってみてほしい。
おまけ1:困ったときにはman
Linuxに慣れていないと,次から次へと出てくるコマンドや情報に迷ってしまうことがある。こんなとき強い味方となるのが,manコマンドだ。Linuxコマンドのヘルプを表示するコマンドである。 manを使っていると,man(1)とかopen(2)とかいう記述を見かけることがある。この数字は,セクションを表している。セクションとは,各コマンドの説明を分類したものだ。例えば,セクション1はユーザー・コマンドの説明,セクション2はシステム・コールの説明となる。面白いところでは,セクション6のゲームの説明だ。したがって,man(1)は,manというコマンドの説明,open(2)は,openというシステム・コールの説明,という意味になる。システム・コールopen( )を調べたかったら man 2 open とすればいい。おまけ2:巨視的視点から見たLinuxカーネル
Linuxソースコードのジャングルが,どれくらいの大きさなのか。「巨視的視点」に立って,定量的に把握しておくことは重要だ。そこで,いくつか定量的な値を導き出すコマンドの使用例を挙げておこう。いずれも,/usr/src/linuxに移動して実行する。
| 調査項目 | コマンド |
| 全ソースコード・ファイル数 | find . -name "*.[chS]" -print | wc | 全ソースコードの行数(ステップ数) | wc -l `find . -name "*.[chS]" -print` | egrep " total" | アーキテクチャ依存部分の行数 | wc -l `find arch -name "*.[chS]" -print` | egrep " total" |
| デバイス・ドライバまわりの行数 | wc -l `find drivers -name "*.[chS]" -print` | egrep " total" |
| ファイル・システムまわりの行数 | wc -l `find fs -name "*.[chS]" -print` | egrep " total" |
| カーネルまわりの行数 | wc -l `find kernel -name "*.[chS]" -print` | egrep " total" |
| メモリー管理まわりの行数 | wc -l `find mm -name "*.[chS]" -print` | egrep " total" |
| ネットワークまわりの行数 | wc -l `find net -name "*.[chS]" -print` | egrep " total" |
| ディレクトリ構造を調べる | find . -type d -maxdepth 2 -print |
wcコマンドは,-lをつけるとソースコードの行数がカウントされるが,オプションをつけないと, 954455 3387787 27923116 のように三つの数字が表示される。これらは左から,行数,語数,バイト数を表す。これらのコマンドで算出される数値は,カーネルのバージョンやインストールの状況によって変わってくるので,だれもが同じ値を示すとは限らない。あくまで目安と考えておけばいいだろう。
Linux Kernelメーリング・リスト(英語版)のダイジェスト
Copyright (c)1999 Nikkei Business Publications, Inc. All Rights
Reserved.
このWebサイトに掲載されている記事・写真・図表などの無断転載を禁じます。
詳しくはここをクリックして下さい。