デバイススペシャルファイル
デバイススペシャルファイルデバイスを読み書きするファイルで、mknodコマンドで作成できます。mknodシステムコールはvfs_mknodeからinodeオペレーションで指定されるmknodがコールされ、そこからget_inodeで作成ノードの振る分後、init_special_inode内でデバイスファイルが作成されます。その中のキャラクタデバイスファイルについて追ってみました。
オープンされたデバイスファイルのinode->i_cdevに、デバイスが設定されていなければ、デバイスの検索をし、見つかればそのデバイスをinode->i_cdevに設定しています。デバイスの検索は、静的変数のcdev_mapにつながれたメージャー番号 % 255を配列のインデックスとしたリストとして管理されています。これはデバイスドライバインストール時のregister_chrdevで登録されるものです。
inode->i_cdevにデバイスが設定できれば、デバイスドライバのコールバック関数を、file構造体に設定し、そのデバイスのopen処理をコールすることにより、デバイスの最初の呼び出しを行っています。
static int mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { struct inode *inode; int error = -EPERM; if (dentry->d_inode) return -EEXIST; inode = get_inode(dir->i_sb, mode, dev); if (inode) { d_instantiate(dentry, inode); dget(dentry); error = 0; } return error; }
static struct inode *get_inode(struct super_block *sb, int mode, dev_t dev) { struct inode *inode = new_inode(sb); if (inode) { inode->i_mode = mode; inode->i_uid = 0; inode->i_gid = 0; inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { default: init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_fop = &default_file_ops; break; case S_IFDIR: inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); break; } } return inode; }キャラクターデバイスの場合fileオペレーションにopenメンバーのみ定義されているdef_chr_fops、およびi_rdevにデバイスNOをセットしておしまいです。
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_mode = mode; if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = rdev; } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = rdev; } else if (S_ISFIFO(mode)) inode->i_fop = &def_fifo_fops; else if (S_ISSOCK(mode)) inode->i_fop = &bad_sock_fops; else printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n", mode); }
const struct file_operations def_chr_fops = { .open = chrdev_open, };キャラクターデバイスinodeのinodeオペレーションのopen時のコールバック関数です。キャラクターデバイスファイルがオープンされる毎に呼ばれます。
オープンされたデバイスファイルのinode->i_cdevに、デバイスが設定されていなければ、デバイスの検索をし、見つかればそのデバイスをinode->i_cdevに設定しています。デバイスの検索は、静的変数のcdev_mapにつながれたメージャー番号 % 255を配列のインデックスとしたリストとして管理されています。これはデバイスドライバインストール時のregister_chrdevで登録されるものです。
inode->i_cdevにデバイスが設定できれば、デバイスドライバのコールバック関数を、file構造体に設定し、そのデバイスのopen処理をコールすることにより、デバイスの最初の呼び出しを行っています。
static int chrdev_open(struct inode *inode, struct file *filp) { struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { struct kobject *kobj; int idx; spin_unlock(&cdev_lock); kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj); spin_lock(&cdev_lock); /* Check i_cdev again in case somebody beat us to it while we dropped the lock. */ p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; inode->i_cindex = idx; list_add(&inode->i_devices, &p->list); new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; filp->f_op = fops_get(p->ops); if (!filp->f_op) { cdev_put(p); return -ENXIO; } if (filp->f_op->open) ret = filp->f_op->open(inode,filp); if (ret) cdev_put(p); return ret; }
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) { ・・・・ retry: mutex_lock(domain->lock); for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { ・・・・inode->i_rdevにセットされるデバイスNOのフォーマットです。下位8ビットにマイナー番号、上位8ビットにメジャー番号がセットされています。psで表示されるメジャー番号とマイナー番号はここの値を表示していると推測できます。。データブロックを持つ必要はなく、i_sizeは意味がありません。従ってpsで表示されるメジャー番号とマイナー番号はここの値を表示していると推測できます。
static inline dev_t new_decode_dev(u32 dev) { unsigned major = (dev & 0xfff00) >> 8; unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return MKDEV(major, minor); } #define MAJOR(dev) ((dev)>>8) #define MINOR(dev) ((dev) & 0xff) #define MKDEV(ma,mi) ((ma)<<8 | (mi))構造的にはデバイスファイルも通常のファイルの殆どかわりません。なんとなく分かってはいましたが、あらためて調べてみて、デバイスもファイルと同じように取り扱うlinuxというかunixのシステムのコンセプトに関心させられました。
補足
ramfsみたいなストレージを持たないファイルシステムの通常ディレクトリ作成時も、modeにS_IFDIRを設定してmknodで処理されているようです・・・。static int mkdir(struct inode *dir, struct dentry *dentry, int mode) { int res; mode = (mode & (S_IRWXUGO | S_ISVTX)) | S_IFDIR; res = mknod(dir, dentry, mode, 0); if (!res) inc_nlink(dir); return res; }