aboutsummaryrefslogtreecommitdiffstats
path: root/ipc
diff options
context:
space:
mode:
authorDavidlohr Bueso <dave@stgolabs.net>2016-12-14 18:06:37 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2016-12-14 19:04:08 -0500
commit4ce33ec2e42d4661bf05289e213bc088eecb9132 (patch)
tree912af718b4eb26ed8f756afa4e56806b519aa581 /ipc
parent9ae949fa382b080170f9d3c8bd9dea951cf52ee7 (diff)
ipc/sem: optimize perform_atomic_semop()
This is the main workhorse that deals with semop user calls such that the waitforzero or semval update operations, on the set, can complete on not as the sma currently stands. Currently, the set is iterated twice (setting semval, then backwards for the sempid value). Slowpaths, and particularly SEM_UNDO calls, must undo any altered sem when it is detected that the caller must block or has errored-out. With larger sets, there can occur situations where this involves a lot of cycles and can obviously be a suboptimal use of cached resources in shared memory. Ie, discarding CPU caches that are also calling semop and have the sembuf cached (and can complete), while the current lock holder doing the semop will block, error, or does a waitforzero operation. This patch proposes still iterating the set twice, but the first scan is read-only, and we perform the actual updates afterward, once we know that the call will succeed. In order to not suffer from the overhead of dealing with sops that act on the same sem_num, such (rare) cases use perform_atomic_semop_slow(), which is exactly what we have now. Duplicates are detected before grabbing sem_lock, and uses simple a 32/64-bit hash array variable to based on the sem_num we are working on. In addition add some comments to when we expect to the caller to block. [akpm@linux-foundation.org: coding-style fixes] [colin.king@canonical.com: ensure we left shift a ULL rather than a 32 bit integer] Link: http://lkml.kernel.org/r/20161028181129.7311-1-colin.king@canonical.com Link: http://lkml.kernel.org/r/20160921194603.GB21438@linux-80c1.suse Signed-off-by: Davidlohr Bueso <dbueso@suse.de> Cc: Manfred Spraul <manfred@colorfullife.com> Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'ipc')
-rw-r--r--ipc/sem.c112
1 files changed, 102 insertions, 10 deletions
diff --git a/ipc/sem.c b/ipc/sem.c
index 1ff103299c28..c14883f5a4b5 100644
--- a/ipc/sem.c
+++ b/ipc/sem.c
@@ -115,7 +115,8 @@ struct sem_queue {
115 struct sembuf *sops; /* array of pending operations */ 115 struct sembuf *sops; /* array of pending operations */
116 struct sembuf *blocking; /* the operation that blocked */ 116 struct sembuf *blocking; /* the operation that blocked */
117 int nsops; /* number of operations */ 117 int nsops; /* number of operations */
118 int alter; /* does *sops alter the array? */ 118 bool alter; /* does *sops alter the array? */
119 bool dupsop; /* sops on more than one sem_num */
119}; 120};
120 121
121/* Each task has a list of undo requests. They are executed automatically 122/* Each task has a list of undo requests. They are executed automatically
@@ -587,15 +588,23 @@ SYSCALL_DEFINE3(semget, key_t, key, int, nsems, int, semflg)
587} 588}
588 589
589/** 590/**
590 * perform_atomic_semop - Perform (if possible) a semaphore operation 591 * perform_atomic_semop[_slow] - Attempt to perform semaphore
592 * operations on a given array.
591 * @sma: semaphore array 593 * @sma: semaphore array
592 * @q: struct sem_queue that describes the operation 594 * @q: struct sem_queue that describes the operation
593 * 595 *
596 * Caller blocking are as follows, based the value
597 * indicated by the semaphore operation (sem_op):
598 *
599 * (1) >0 never blocks.
600 * (2) 0 (wait-for-zero operation): semval is non-zero.
601 * (3) <0 attempting to decrement semval to a value smaller than zero.
602 *
594 * Returns 0 if the operation was possible. 603 * Returns 0 if the operation was possible.
595 * Returns 1 if the operation is impossible, the caller must sleep. 604 * Returns 1 if the operation is impossible, the caller must sleep.
596 * Negative values are error codes. 605 * Returns <0 for error codes.
597 */ 606 */
598static int perform_atomic_semop(struct sem_array *sma, struct sem_queue *q) 607static int perform_atomic_semop_slow(struct sem_array *sma, struct sem_queue *q)
599{ 608{
600 int result, sem_op, nsops, pid; 609 int result, sem_op, nsops, pid;
601 struct sembuf *sop; 610 struct sembuf *sop;
@@ -666,6 +675,72 @@ undo:
666 return result; 675 return result;
667} 676}
668 677
678static int perform_atomic_semop(struct sem_array *sma, struct sem_queue *q)
679{
680 int result, sem_op, nsops;
681 struct sembuf *sop;
682 struct sem *curr;
683 struct sembuf *sops;
684 struct sem_undo *un;
685
686 sops = q->sops;
687 nsops = q->nsops;
688 un = q->undo;
689
690 if (unlikely(q->dupsop))
691 return perform_atomic_semop_slow(sma, q);
692
693 /*
694 * We scan the semaphore set twice, first to ensure that the entire
695 * operation can succeed, therefore avoiding any pointless writes
696 * to shared memory and having to undo such changes in order to block
697 * until the operations can go through.
698 */
699 for (sop = sops; sop < sops + nsops; sop++) {
700 curr = sma->sem_base + sop->sem_num;
701 sem_op = sop->sem_op;
702 result = curr->semval;
703
704 if (!sem_op && result)
705 goto would_block; /* wait-for-zero */
706
707 result += sem_op;
708 if (result < 0)
709 goto would_block;
710
711 if (result > SEMVMX)
712 return -ERANGE;
713
714 if (sop->sem_flg & SEM_UNDO) {
715 int undo = un->semadj[sop->sem_num] - sem_op;
716
717 /* Exceeding the undo range is an error. */
718 if (undo < (-SEMAEM - 1) || undo > SEMAEM)
719 return -ERANGE;
720 }
721 }
722
723 for (sop = sops; sop < sops + nsops; sop++) {
724 curr = sma->sem_base + sop->sem_num;
725 sem_op = sop->sem_op;
726 result = curr->semval;
727
728 if (sop->sem_flg & SEM_UNDO) {
729 int undo = un->semadj[sop->sem_num] - sem_op;
730
731 un->semadj[sop->sem_num] = undo;
732 }
733 curr->semval += sem_op;
734 curr->sempid = q->pid;
735 }
736
737 return 0;
738
739would_block:
740 q->blocking = sop;
741 return sop->sem_flg & IPC_NOWAIT ? -EAGAIN : 1;
742}
743
669static inline void wake_up_sem_queue_prepare(struct sem_queue *q, int error, 744static inline void wake_up_sem_queue_prepare(struct sem_queue *q, int error,
670 struct wake_q_head *wake_q) 745 struct wake_q_head *wake_q)
671{ 746{
@@ -1720,9 +1795,10 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
1720 struct sembuf fast_sops[SEMOPM_FAST]; 1795 struct sembuf fast_sops[SEMOPM_FAST];
1721 struct sembuf *sops = fast_sops, *sop; 1796 struct sembuf *sops = fast_sops, *sop;
1722 struct sem_undo *un; 1797 struct sem_undo *un;
1723 int undos = 0, alter = 0, max, locknum; 1798 int max, locknum;
1799 bool undos = false, alter = false, dupsop = false;
1724 struct sem_queue queue; 1800 struct sem_queue queue;
1725 unsigned long jiffies_left = 0; 1801 unsigned long dup = 0, jiffies_left = 0;
1726 struct ipc_namespace *ns; 1802 struct ipc_namespace *ns;
1727 1803
1728 ns = current->nsproxy->ipc_ns; 1804 ns = current->nsproxy->ipc_ns;
@@ -1736,10 +1812,12 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
1736 if (sops == NULL) 1812 if (sops == NULL)
1737 return -ENOMEM; 1813 return -ENOMEM;
1738 } 1814 }
1815
1739 if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) { 1816 if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) {
1740 error = -EFAULT; 1817 error = -EFAULT;
1741 goto out_free; 1818 goto out_free;
1742 } 1819 }
1820
1743 if (timeout) { 1821 if (timeout) {
1744 struct timespec _timeout; 1822 struct timespec _timeout;
1745 if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) { 1823 if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) {
@@ -1753,17 +1831,30 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
1753 } 1831 }
1754 jiffies_left = timespec_to_jiffies(&_timeout); 1832 jiffies_left = timespec_to_jiffies(&_timeout);
1755 } 1833 }
1834
1756 max = 0; 1835 max = 0;
1757 for (sop = sops; sop < sops + nsops; sop++) { 1836 for (sop = sops; sop < sops + nsops; sop++) {
1837 unsigned long mask = 1ULL << ((sop->sem_num) % BITS_PER_LONG);
1838
1758 if (sop->sem_num >= max) 1839 if (sop->sem_num >= max)
1759 max = sop->sem_num; 1840 max = sop->sem_num;
1760 if (sop->sem_flg & SEM_UNDO) 1841 if (sop->sem_flg & SEM_UNDO)
1761 undos = 1; 1842 undos = true;
1762 if (sop->sem_op != 0) 1843 if (dup & mask) {
1763 alter = 1; 1844 /*
1845 * There was a previous alter access that appears
1846 * to have accessed the same semaphore, thus use
1847 * the dupsop logic. "appears", because the detection
1848 * can only check % BITS_PER_LONG.
1849 */
1850 dupsop = true;
1851 }
1852 if (sop->sem_op != 0) {
1853 alter = true;
1854 dup |= mask;
1855 }
1764 } 1856 }
1765 1857
1766
1767 if (undos) { 1858 if (undos) {
1768 /* On success, find_alloc_undo takes the rcu_read_lock */ 1859 /* On success, find_alloc_undo takes the rcu_read_lock */
1769 un = find_alloc_undo(ns, semid); 1860 un = find_alloc_undo(ns, semid);
@@ -1828,6 +1919,7 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
1828 queue.undo = un; 1919 queue.undo = un;
1829 queue.pid = task_tgid_vnr(current); 1920 queue.pid = task_tgid_vnr(current);
1830 queue.alter = alter; 1921 queue.alter = alter;
1922 queue.dupsop = dupsop;
1831 1923
1832 error = perform_atomic_semop(sma, &queue); 1924 error = perform_atomic_semop(sma, &queue);
1833 if (error == 0) { /* non-blocking succesfull path */ 1925 if (error == 0) { /* non-blocking succesfull path */