Down operation: in the Linux kernel, the down operation on semaphores is as follows:
- Void down (struct semaphore * SEM); // It cannot be interrupted.
- Int down_interruptible (struct semaphore * SEM); // can be interrupted
- Int down_killable (struct semaphore * SEM); // sleep processes can be awakened due to a fatal signal and the semaphore acquisition operation is interrupted.
- Int down_trylock (struct semaphore * SEM); // attempts to obtain the semaphore. If it cannot be obtained, 1 is returned without sleep. If 0 is returned, the semaphore is obtained.
- Int down_timeout (struct semaphore * SEM, long jiffies); // indicates that the sleep time is limited. If the semaphore still cannot be obtained at the time specified by jiffies, the error code is returned.
Among the above four functions, the most frequently used driver is the down_interruptible function, which will be analyzed below.
The down_interruptible function is defined as follows:
int down_interruptible(struct semaphore *sem){ unsigned long flags; int result = 0; spin_lock_irqsave(&sem->lock,flags); if (likely(sem->count> 0)) sem->count--; else result =__down_interruptible(sem); spin_unlock_irqrestore(&sem->lock,flags); return result;}
Function Analysis:The function first guarantees the atomicity of the SEM-> count operation by calling the spin_lock_irqsave function. If the count is greater than 0, the current process can obtain the semaphore, and the count value is reduced by 1 and then exited. If the Count value is not greater than 0, it indicates that the current process cannot obtain the semaphore, call _ down_interruptible, and the latter will continue to call _ down_common.
The _ down_common function is defined as follows:
static inline int __sched __down_common(struct semaphore *sem, longstate, longtimeout){ struct task_struct *task= current; struct semaphore_waiterwaiter; list_add_tail(&waiter.list,&sem->wait_list); waiter.task = task; waiter.up = 0; for (;;) { if(signal_pending_state(state, task)) gotointerrupted; if (timeout <=0) gototimed_out; __set_task_state(task,state); spin_unlock_irq(&sem->lock); timeout =schedule_timeout(timeout); spin_lock_irq(&sem->lock); if (waiter.up) return 0; } timed_out: list_del(&waiter.list); return -ETIME; interrupted: list_del(&waiter.list); return -EINTR;}
Function Analysis:The following operations are performed on the number of _ down_common functions.
(1) place the current process in the queue managed by the semaphore member variable wait_list.
(2) In a for loop, the current process status is task_interruptible. When schedule_timeout is called to make the current process sleep, the function will stay on the schedule_timeout call, the task is scheduled again.
(3) when the process is scheduled again, perform the corresponding action based on the reason: if waiter. if the value of up is not 0, the process is awakened by the up operation of the semaphore. The process can obtain the semaphore. If the process is awakened due to the interruption of the user space signal or timeout signal, the corresponding error code is returned.
Up Operation: Only one up function is provided in the Linux kernel.
The up function is defined as follows:
void up(struct semaphore *sem){ unsigned long flags; spin_lock_irqsave(&sem->lock,flags); if(likely(list_empty(&sem->wait_list))) sem->count++; else __up(sem); spin_unlock_irqrestore(&sem->lock,flags);}
Function Analysis:If the wait_list queue of SEM is empty, it indicates that no other process is waiting for the semaphore. You only need to add the Count of SEM to 1. If the wait_list queue is not empty, it indicates that other processes are sleeping on wait_list waiting for this signal. At this time, _ up (SEM) is called to wake up the process:
The _ up () function is defined as follows:
static noinline void __sched __up(struct semaphore *sem){ struct semaphore_waiter*waiter = list_first_entry(&sem->wait_list, structsemaphore_waiter, list); list_del(&waiter->list); waiter->up = 1; wake_up_process(waiter->task);}
Function Analysis:In the function, wake_up_process is called to wake up the process, so that the process will wake up from the timeout = schedule_timeout (timeout) in the previous _ down_interruptible call. Wait-up = 1, _ down_interruptible returns 0, and the process obtains the semaphore.
The relationship between up () and down () functions:From the analysis of the two functions above, we can know that timeout = schedule_timeout (timeout) plays an important role in the __down_common function.