読者です 読者をやめる 読者になる 読者になる

Linux SystemV IPC: semop()の実装を読む

kernel linux

今日はsemop()の実装を。システムコール的にはsemop(2)とsemtimedop(2)の2つの関数がありますが、カーネルsemop()側はsemtimedop(semid, sops, nsops, NULL);と呼び出してるだけなので、実体は一つです。

では、semop(2)とsemtimedop(2)の共通実装部分、というかsemtimedop(2)の実装を見ていきます。最初は変数定義なので飛ばそうと思うんだけど、sops = fast_sopsは後で出てくるので覚えときます。

1774 SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
1775                 unsigned, nsops, const struct timespec __user *, timeout)
1776 {
1777         int error = -EINVAL;
1778         struct sem_array *sma;
1779         struct sembuf fast_sops[SEMOPM_FAST];
1780         struct sembuf *sops = fast_sops, *sop;
1781         struct sem_undo *un;
1782         int undos = 0, alter = 0, max, locknum;
1783         struct sem_queue queue;
1784         unsigned long jiffies_left = 0;
1785         struct ipc_namespace *ns;
1786         struct list_head tasks;

カレントプロセスのnamespaceからipc namespaceを取得します。そして引数のチェックです。
最後のnsops > SEMOPM_FASTが先ほど覚えておこうと言ったところにつながります。

1788         ns = current->nsproxy->ipc_ns;
1789 
1790         if (nsops < 1 || semid < 0)
1791                 return -EINVAL;
1792         if (nsops > ns->sc_semopm)
1793                 return -E2BIG;
1794         if (nsops > SEMOPM_FAST) {
1795                 sops = kmalloc(sizeof(*sops)*nsops, GFP_KERNEL);
1796                 if (sops == NULL)
1797                         return -ENOMEM;
1798         }

SEMOPM_FASTはipc/sem.cで定義されていて、操作するセマフォの数が64個以下ならstack上のデータ(fast_sops)、それ以上ならメモリを確保するというふうに処理しています。

154 #define SEMOPM_FAST     64  /* ~ 372 bytes on stack */

ユーザーランドからのデータをコピーします。

1799         if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) {
1800                 error =  -EFAULT;
1801                 goto out_free;
1802         }

ここはsemtimedop(2)の場合の処理です。やっているのはtimeoutのデータをユーザーランドからコピーして、設定値のチェック。そして、struct timespecをjiffiesに変換します。

803         if (timeout) {
1804                 struct timespec _timeout;
1805                 if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) {
1806                         error = -EFAULT;
1807                         goto out_free;
1808                 }
1809                 if (_timeout.tv_sec < 0 || _timeout.tv_nsec < 0 ||
1810                         _timeout.tv_nsec >= 1000000000L) {
1811                         error = -EINVAL;
1812                         goto out_free;
1813                 }
1814                 jiffies_left = timespec_to_jiffies(&_timeout);
1815         }

次にセマフォ1つ1つに対して値のチェック・フラグの設定を行います。

1816         max = 0;
1817         for (sop = sops; sop < sops + nsops; sop++) {
1818                 if (sop->sem_num >= max)
1819                         max = sop->sem_num;
1820                 if (sop->sem_flg & SEM_UNDO)
1821                         undos = 1;
1822                 if (sop->sem_op != 0)
1823                         alter = 1;
1824         }

このリストは後々の処理で起床させるタスクを繋ぐのに使用しています。

1826         INIT_LIST_HEAD(&tasks);

undosはstruct sembufのsem_flgにSEM_UNDOが設定されていた場合に1になっています。セットしているのは上述のループ部です。undo操作は別途読もうということにして、ここでは飛ばします。そうすると実行するのはrcu_read_lock()となります。

1828         if (undos) {
1829                 /* On success, find_alloc_undo takes the rcu_read_lock */
1830                 un = find_alloc_undo(ns, semid);
1831                 if (IS_ERR(un)) {
1832                         error = PTR_ERR(un);
1833                         goto out_free;
1834                 }
1835         } else {
1836                 un = NULL;
1837                 rcu_read_lock();
1838         }

semidに該当するオブジェクトがnamespace内にあるかチェックと取得。

1840         sma = sem_obtain_object_check(ns, semid);
1841         if (IS_ERR(sma)) {
1842                 rcu_read_unlock();
1843                 error = PTR_ERR(sma);
1844                 goto out_free;
1845         }

セマフォの数が大きくなり過ぎたらエラーです。maxは先に見たループで設定しています。

1847         error = -EFBIG;
1848         if (max >= sma->sem_nsems)
1849                 goto out_rcu_wakeup;

パーミッションのチェックです。

1851         error = -EACCES;
1852         if (ipcperms(ns, &sma->sem_perm, alter ? S_IWUGO : S_IRUGO))
1853                 goto out_rcu_wakeup;

security_と付いているようにここはselinux等の処理なので飛ばします。

1855         error = security_sem_semop(sma, sops, nsops, alter);
1856         if (error)
1857                 goto out_rcu_wakeup;

ロックを取ります。sem_lock()はsops->sem_numもしくは-1を返します。

1859         error = -EIDRM;
1860         locknum = sem_lock(sma, sops, nsops);

オブジェクトのチェック。

1869         if (!ipc_valid_object(&sma->sem_perm))
1870                 goto out_unlock_free;

sem_flgにSEM_UNDOが指定されていた場合のエラーチェック。

1878         if (un && un->semid == -1)
1879                 goto out_unlock_free;

queueの型はstruct queueです。定義場所はipc/sem.cなのでここでしか使われないはずです。

1881         queue.sops = sops;
1882         queue.nsops = nsops;
1883         queue.undo = un;
1884         queue.pid = task_tgid_vnr(current);
1885         queue.alter = alter;

構造体の定義はこのようになっています。寝ているプロセスをsem_queueに設定してリストで繋ぐ感じですね。

104 /* One queue for each sleeping process in the system. */
105 struct sem_queue {
106         struct list_head        list;    /* queue of pending operations */
107         struct task_struct      *sleeper; /* this process */
108         struct sem_undo         *undo;   /* undo structure */
109         int                     pid;     /* process id of requesting process */
110         int                     status;  /* completion status of operation */
111         struct sembuf           *sops;   /* array of pending operations */
112         struct sembuf           *blocking; /* the operation that blocked */
113         int                     nsops;   /* number of operations */
114         int                     alter;   /* does *sops alter the array? */
115 };

perform_atomic_semop()はnsops個のセマフォを操作します。すべて実行できたら0が返ります。一つでも操作ができなかった場合は-EAGAINが返ります(IPC_NOWAITがsem_flgに設定されていた場合)。IPC_NOWAITが設定されていない場合は1が返ります。 if分に入って、alterはsem_opが0じゃなかった場合に1になります。
do_smart_update()は大雑把に説明するとdo_smart_wakeup_zero()を呼んでstruct semのsemvalが0なプロセスを起床してセマフォ操作を実行させるものです。
set_semotime()はstruct semsem_otimeにUNIX時刻を設定します。

1887         error = perform_atomic_semop(sma, &queue);
1888         if (error == 0) {
1889                 /* If the operation was successful, then do
1890                  * the required updates.
1891                  */
1892                 if (alter)
1893                         do_smart_update(sma, sops, nsops, 1, &tasks);
1894                 else
1895                         set_semotime(sma, sops);
1896         }

perform_atomic_semop()より-EAGAINが返った場合はここで終了です。

1897         if (error <= 0)
1898                 goto out_unlock_free;

次ですが、操作するセマフォ数が1つの時と複数の時で処理が違っていますがいづれにせよqueueのリストにpending_alterもしくはpending_constが繋がっていきます。

1904         if (nsops == 1) {
1905                 struct sem *curr;
1906                 curr = &sma->sem_base[sops->sem_num];
1907 
1908                 if (alter) {
1909                         if (sma->complex_count) {
1910                                 list_add_tail(&queue.list,
1911                                                 &sma->pending_alter);
1912                         } else {
1913 
1914                                 list_add_tail(&queue.list,
1915                                                 &curr->pending_alter);
1916                         }
1917                 } else {
1918                         list_add_tail(&queue.list, &curr->pending_const);
1919                 }
1920         } else {
1921                 if (!sma->complex_count)
1922                         merge_queues(sma);
1923 
1924                 if (alter)
1925                         list_add_tail(&queue.list, &sma->pending_alter);
1926                 else
1927                         list_add_tail(&queue.list, &sma->pending_const);
1928 
1929                 sma->complex_count++;
1930         }

この次からの処理のための初期設定です。

1932         queue.status = -EINTR;
1933         queue.sleeper = current;
1934 

カレントプロセスを割り込み可能にして今掛けられているlockを外します。gotoのラベル「sleep_again」があるので、またここに戻ってくることもあります。

1935 sleep_again:
1936         current->state = TASK_INTERRUPTIBLE;
1937         sem_unlock(sma, locknum);
1938         rcu_read_unlock();

timeoutの設定がある場合は指定された時間待ち、指定が無ければ別のプロセスに実行を譲ります。

1940         if (timeout)
1941                 jiffies_left = schedule_timeout(jiffies_left);
1942         else
1943                 schedule();

キューにつないだプロセスに変化があったかを確認します。

1963         /*
1964          * Wait until it's guaranteed that no wakeup_sem_queue_do() is ongoing.
1965          */
1945         error = get_queue_result(&queue);

エラーチェック。

1968         /*
1969          * Array removed? If yes, leave without sem_unlock().
1970          */
1971         if (IS_ERR(sma)) {
1972                 rcu_read_unlock();
1973                 goto out_free;
1974         }

sleep_againラベルの前でqueue.statusを-EINTRにしているのでget_queue_result()の返り値が-EINTRでないということは誰か他のプロセスによって起床したということで終了処理に飛ぶ。

1977         /*
1978          * If queue.status != -EINTR we are woken up by another process.
1979          * Leave without unlink_queue(), but with sem_unlock().
1980          */
1981         if (error != -EINTR)
1982                 goto out_unlock_free;

タイムアウトした場合は-EAGAINを設定。

1984         /*
1985          * If an interrupt occurred we have to clean up the queue
1986          */
1987         if (timeout && jiffies_left == 0)
1988                 error = -EAGAIN;

queue.statusが相変わらず-EINTRかつ保留中のシグナルがない場合はsleep_againに戻る。

1990         /*
1991          * If the wakeup was spurious, just retry
1992          */
1993         if (error == -EINTR && !signal_pending(current))
1994                 goto sleep_again;

ここからは関数を抜ける前の終了処理です。

1996         unlink_queue(sma, &queue);
1997 
1998 out_unlock_free:
1999         sem_unlock(sma, locknum);
2000 out_rcu_wakeup:
2001         rcu_read_unlock();
2002         wake_up_sem_queue_do(&tasks);
2003 out_free:
2004         if (sops != fast_sops)
2005                 kfree(sops);
2006         return error;
2007 }

Androidのなかみ InsideAndroid

Androidのなかみ InsideAndroid