勧告ロックと強制ロック
Rev.1を表示中。最新版はこちら。
勧告ロックと強制ロック通常ファイルのロックは勧告ロックとなり、セマフォの排他処理のように、プロセス間で必要なロックを取得してファイル操作を行うことで、ファイルの読み書きの処理を行うようになっている。これを勧告ロック(アドバイザリロック)と言い。一方ロックをかけたファイルは問答無用にロックの処理をおこなうのを強制ロック言う。
(Linuxではファイルロックの種類とし、flock/fctrl/releseの3種類あるが、強制ロックの概念はこれと直接関係ない。)
強制ロックはfctrlシステムコール(POSIXロック)下でサポートされているよう。なお、flockシステムコールにLOCK_MANDオプションがあり、カーネルに強制ロックをおこなう関数が組み込まれているが、現行カーネルではファイル読み書きにおいて、その処理はバインドされていない模様・・・。
fctrlシステムコール実装での、勧告ロック/強制ロックの違いはない。fctrlシステムコールの通常の処理は、勧告ロックなのだが、mandオプションでmountしたファイルシステム下のファイルで、そのファイルのセットグループIDを設定し、グループ実行をクリアすることで、そのファイルは強制ロックのファイルとなる。セットグループIDとグループ実行ビットは2つで意味をなすもので、セットグループIDとセットして、グループ実行ビットをクリアする有り得ない組み合わせを、強制ロックとして代用してる。
勧告ロックは、ファイル処理時ロックを取得するようプログラムしていく訳だが、強制ロックはカーネルの読み書き処理内で、ロックの有無をチェックしてくれる。writeシステムコールは、vfs_write()でその処理が行われるが、ここでrw_verify_area()をコールする事で強制ロックのチェックを行うことになる。
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret; if (!(file->f_mode & FMODE_WRITE)) return -EBADF; if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write)) return -EINVAL; if (unlikely(!access_ok(VERIFY_READ, buf, count))) return -EFAULT; ret = rw_verify_area(WRITE, file, pos, count); if (ret >= 0) { count = ret; if (file->f_op->write) ret = file->f_op->write(file, buf, count, pos); else ret = do_sync_write(file, buf, count, pos); if (ret > 0) { fsnotify_modify(file); add_wchar(current, ret); } inc_syscw(current); } return ret; }rw_verify_area()は、まず指定する領域の引数の正当性をチェックした後、inode->i_flock && mandatory_lock(inode)で、強制ロックなら、locks_mandatory_area()でその領域がロック対象かどうかチェックする。POSIXロックはflockと違い、ファイルの任意の領域にロックを設定する事が可能である。
int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count) { struct inode *inode; loff_t pos; int retval = -EINVAL; inode = file->f_path.dentry->d_inode; if (unlikely((ssize_t) count < 0)) return retval; pos = *ppos; if (unlikely(pos < 0)) { if (!unsigned_offsets(file)) return retval; if (count >= -pos) /* both values are in 0..LLONG_MAX */ return -EOVERFLOW; } else if (unlikely((loff_t) (pos + count) < 0)) { if (!unsigned_offsets(file)) return retval; } if (unlikely(inode->i_flock && mandatory_lock(inode))) { retval = locks_mandatory_area( read_write == READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE, inode, file, pos, count); if (retval < 0) return retval; } retval = security_file_permission(file, read_write == READ ? MAY_READ : MAY_WRITE); if (retval) return retval; return count > MAX_RW_COUNT ? MAX_RW_COUNT : count; }IS_MANDLOCK(inode)マクロは、inodeのスーパブロックのs_flagsが、MS_MANDLOCKが設定されているかどうかチェックする。これはmandオプションでのmount時に設定されている。__mandatory_lock()はinodeにセットグループIDが設定されていて、グループ実行が設定されていない事をチェックする。
#define IS_MANDLOCK(inode) __IS_FLG(inode, MS_MANDLOCK) #define __IS_FLG(inode,flg) ((inode)->i_sb->s_flags & (flg)) static inline int mandatory_lock(struct inode *ino) { return IS_MANDLOCK(ino) && __mandatory_lock(ino); } static inline int __mandatory_lock(struct inode *ino) { return (ino->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID; }locks_mandatory_area()でロックエリアをチェックするのだが、そのロックオブジェクトは__posix_lock_file()で、fctrlシステムコールで設定されたロックしかチェックしていない。
int locks_mandatory_area(int read_write, struct inode *inode, struct file *filp, loff_t offset, size_t count) { struct file_lock fl; int error; locks_init_lock(&fl); fl.fl_owner = current->files; fl.fl_pid = current->tgid; fl.fl_file = filp; fl.fl_flags = FL_POSIX | FL_ACCESS; if (filp && !(filp->f_flags & O_NONBLOCK)) fl.fl_flags |= FL_SLEEP; fl.fl_type = (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK; fl.fl_start = offset; fl.fl_end = offset + count - 1; for (;;) { error = __posix_lock_file(inode, &fl, NULL); if (error != FILE_LOCK_DEFERRED) break; error = wait_event_interruptible(fl.fl_wait, !fl.fl_next); if (!error) { if (__mandatory_lock(inode)) continue; } locks_delete_block(&fl); break; } return error; }なお、flockシステムコール用の上記のロック処理として、カーネルは、lock_may_write()を提供している。これはfl->fl_type & LOCK_MANDで強制ロックとしており、この設定はLOCK_MANDオプションでのflockシステムコールで設定される。なお本関数はカーネル内から使用されていない。
int lock_may_write(struct inode *inode, loff_t start, unsigned long len) { struct file_lock *fl; int result = 1; lock_flocks(); for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) { if (IS_POSIX(fl)) { if ((fl->fl_end < start) || (fl->fl_start > (start + len))) continue; } else if (IS_FLOCK(fl)) { if (!(fl->fl_type & LOCK_MAND)) continue; if (fl->fl_type & LOCK_WRITE) continue; } else continue; result = 0; break; } unlock_flocks(); return result; } EXPORT_SYMBOL(lock_may_write);