日経ソフトウエア99年9月号特集1
「ソースコードを読もう!」補足記事

by よしおか ひろたか

Updated on 1999/08/09
Sorry, Japanese only.

このコンテンツは,特集「ソースコードを読もう!」第1部「Linuxカーネルの探検に出発!」で,誌面では紹介しきれなかった解説を補足したものです。本誌を読んでいることを前提にしていますが,LinuxやC言語の表記についての基礎的な知識も必要です。また,本誌の内容と一部重なる部分もあります。あらかじめご了承ください。(編集部)


(1)Linuxのファイル・システムを理解するための三つの構造体
 本誌78ページで解説している構造体の説明の補足です

(2)VFSによるマウントのようすを理解する
 本誌79ページのVFSによるファイル・システムのマウントについて,もう少し詳しく解説しています

(3)システム・コールWrite()を読む
 システム・コールwrite( )のコードを読みます。

(4)Linuxカーネル探検おまけの知識
 カーネル探検で役立つちょっとした情報です

(5)Linuxカーネル探検に役立つ情報ソース


(1)ファイル・システムを理解するための三つの構造体

 Linuxのファイル・システムを理解するうえで,ポイントとなるデータ構造は三つある。
(1)file構造体
(2)inode構造体
(3)super_block構造体
である。ファイルに関する情報はfile構造体が,ファイル内部に関する情報はinode構造体が,そしてファイル・システムに関する情報はsuper_block構造体がそれぞれ持っている。各構造体の中身を見てみよう。


file構造体(/usr/src/linux/include/linux/fs.h)

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することがわかる。
return default_llseek(file, offset, origin);
とかにはならないので,default_llseek()が呼ばれることがあるということを注意しないといけない。またfile->f_op->llseekの実体はファイル・システムにより異なる。例えば,ext2では
/*
 * 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の実体になる
inode構造体

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を参照してほしい。
supter_block構造体

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 ディレクトリにあるので参考にしてほしい。

このページのトップへ

(2)VFSによるマウントのようすを理解する

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()はどこからよばれているのか。それを追及したくなるのが人情である。
grep -n 'mount_root' `find .. -name "*.[chS]" -print`
でgrepしてみよう(一つ上のディレクトリ(..)でfindしているのに注意してほしい)。

../init/main.c:1340:mount_root();
main.c でよばれているのがわかる。
さっそくそれを見る。そうすると,do_basic_setup() で呼んでいる(/usr/src/linux/init/main.c)。
/*
 * 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() ではファイル・システムだけでなく,各種デーモンなども起動しているようである。
ps -el
などをすればそれらのプロセスID,親のプロセスなどがわかる。

私たちはカーネルの初期化を逆流している。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の文字が見える。というわけで
register_filesystem(&ext2_fs_type);
によって,ext2ファイル・システムが登録されたことがわかった。そして,filesystem_setup()によって,各種ファイル・システムが登録されることになる。ここでもう一度,mount_root()を見てみよう。
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というのが表れていないので,そのコントロール・フローを追うのは難しい。
ユーザーレベルのプログラムであればデバッガを利用して,注目した関数にブレークポイントを設定することによって,関数の呼び出し関係を簡単に表示することができるが,カーネルの場合は,なかなかそうもいかないことが,理解を難しくする要因の一つとなっている。

このページのトップへ

(3)システム・コールWrite()を読む

システム・コール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; 
} 
ちょっと大物ではあるが,各自ソースを追ってみてほしい。

このページのトップへ

(4)Linuxカーネル探検おまけの知識

おまけ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 のように三つの数字が表示される。これらは左から,行数,語数,バイト数を表す。これらのコマンドで算出される数値は,カーネルのバージョンやインストールの状況によって変わってくるので,だれもが同じ値を示すとは限らない。あくまで目安と考えておけばいいだろう。
このページのトップへ

(5)カーネル探検に役立つ情報ソース

カーネル・ソースのクロス・リファレンス

日本のLinux情報

Linux Kernelメーリング・リスト(英語版)のダイジェスト

日本のLinux関係のメーリング・リスト

横浜Linuxユーザー会


Copyright (c)1999 Nikkei Business Publications, Inc. All Rights Reserved.
このWebサイトに掲載されている記事・写真・図表などの無断転載を禁じます。
詳しくはここをクリックして下さい。