The Master said, “I have never seen a man so good as a womanizer.” The Analects of Confucius: A rare chapter

A hundred blog series. This is:V34. Xx HongMeng kernel source code analysis (atomic operation) | who escort for atomic operations

This article explains atomic operations

Before reading this article, it is recommended to read hongmeng kernel source code analysis (total directory) series.

The basic concept

In an operating system that supports multitasking, modifying data in an area of memory involves three steps: read, modify, and write. However, the data in the same memory area may be accessed by multiple tasks at the same time. If the data is interrupted by other tasks in the process of data modification, the execution result of the operation will be unpredictable.

The method of switch interrupt can guarantee the result of multi-task, but it will obviously affect the system performance.

The ARMv6 architecture introduces LDREX and STREX instructions to support more rigorous non-blocking synchronization of shared memory. The atomic operation thus implemented ensures that the read-modify-write operation on the same data will not be interrupted during its execution, i.e. the atomicity of the operation.

When multiple tasks add, subtract, or swap the same memory data, atomic operations are used to ensure predictable results.

The essence of the spin lock is atomic operation of a variable, and must be achieved through assembly code, that is to say, LDREX and STREX instructions ensure the underlying implementation of atomic operations. Review the assembly code for applying and releasing a spin lock.

ArchSpinLock request lock code

    FUNCTION(ArchSpinLock) @hold on to lock mov R1, #1      @r1=1
    1: @ Function of the loop, since SEV is a broadcast event. [r0] @r0 = &lock->rawLock CMP r2, #0@ r2 and0Strexeq r2, R1, [r0]@ At this time, the CPU is awakened, try lock->rawLock=1, r2=0Cmpeq r2, #0At sign and let's see if R2 is equal to0If it is equal, the lock BNE is obtained1bIf @ is not equal, continue to enter the loop DMB @ with DMB instruction to isolate, to ensure that the buffer data has been implemented in RAM BXLR @ must be locked, jump back to call ArchSpinLock functionCopy the code

ArchSpinUnlock releases the lock code

    FUNCTION(ArchSpinUnlock) @release lock mov R1, #0          @r1=0DMB @ Data store isolation to ensure that cached data has been implemented into RAM STR R1, [r0] @ command lock->rawLock =0DSB @ data synchronization isolation SEv @ broadcast events to each CPU, wake up the sleeping CPU Bx lr @ jump back to call ArchSpinLock functionCopy the code

Operation mechanism

Hongmeng provides users with a set of atomic operation interfaces by encapsulating LDREX and STREX in ARMv6 architecture.

  • LDREX Rx, [Ry] reads the value in memory and marks exclusive access to this segment of memory:

    • Read 4 bytes of memory data pointed to by register Ry and save to register Rx.
    • Add an exclusive access tag to the memory region pointed to by Ry.
  • STREX Rf, Rx, [Ry] check whether memory has an exclusive access mark, if so, update the memory value and empty the mark, otherwise do not update memory:

    • There are exclusive access flags
      • Update the value in register Rx to the memory pointed to by register Ry.
      • The flag register Rf is set to 0.
    • There are no exclusive access tags
      • Memory is not updated.
      • The flag register Rf is set to 1.
  • Judge flag register When the flag register is 0, exit the loop and the atomic operation ends. When the flag register is 1, the loop continues and the atomic operation is repeated.

Feature list

Atomic data contains two types: Atomic (signed 32 bits) and Atomic64 (signed 64 bits). Atomic operation module provides users with the following functions, interface details can be viewed source code.

LOS_AtomicAdd, LOS_AtomicSub, LOS_AtomicRead, LOS_AtomicSet understand function assembly code is the key to understanding atomic operations.

LOS_AtomicAdd

// Add data in memory
STATIC INLINE INT32 LOS_AtomicAdd(Atomic *v, INT32 addVal)	
{
    INT32 val;
    UINT32 status;

    do {
        __asm__ __volatile__("ldrex   %1, [%2]\n"
                             "Add %1, %1, %3\n" 
                             "Strex %0, %1, [%2]"
                             : "=&r"(status),"=&r"(val)
                             : "r"(v),"r"(addVal)
                             : "cc");
    } while(__builtin_expect(status ! =0.0));

    return val;
}
Copy the code

This is a section of C language embedded assembly, one by one interpretation

    1. First thestatus val v addValThe value of is taken over by general purpose registers (R0~R3).
    1. %2 represents the input parameter v, and [%2] represents the value of the parameter v to the address, *v, which is the exclusive function
    1. %0 to %3 correspondstatus val v addVal
    1. Ldrex %1, [%2] : val = *v;
    1. Add %1, %1, %3 = val + addVal;
    1. Strex %0, %1, [%2] *v = val;
    1. Status indicates whether the update is successful. If the update is successful, the value is 0. If the update is not successful, the value is 1
    1. __builtin_expect is the judgment statement that ends the loop, telling the compiler which branch is most likely to be executed. This command is written as __builtin_expect(EXP, N).

      The probability that e to the N is equal to N is high.

      __builtin_expect(status! = 0, 0)

      If strex is not strex (status == 0), the strex will be strex (status == 0).

    1. “=&r”(val) the decorated operator serves as output, passing the value of the register back to val, which is the return value of the function
    1. “Cc” declares the above information to the compiler.

LOS_AtomicSub

// Subtract memory data
STATIC INLINE INT32 LOS_AtomicSub(Atomic *v, INT32 subVal)	
{
    INT32 val;
    UINT32 status;

    do {
        __asm__ __volatile__("ldrex   %1, [%2]\n"
                             "Sub %1, %1, %3\n"
                             "Strex %0, %1, [%2]"
                             : "=&r"(status),"=&r"(val)
                             : "r"(v),"r"(subVal)
                             : "cc");
    } while(__builtin_expect(status ! =0.0));

    return val;
}
Copy the code

Interpretation of the

  • withLOS_AtomicAddInterpretation of the

volatile

The important point here is that volatile tells the compiler that the variable it defines is subject to change at any time, so that the compiled program reads directly from the address of the variable every time it needs to store or read the variable. Without the volatile keyword, the compiler may optimize reading and storage, may temporarily use a value in a register, and may be inconsistent if the variable is updated by another program.

// Read memory data
STATIC INLINE INT32 LOS_AtomicRead(const Atomic *v)	
{
    return* (volatile INT32 *)v;
}
// Write memory data
STATIC INLINE VOID LOS_AtomicSet(Atomic *v, INT32 setVal)	
{
    *(volatile INT32 *)v = setVal;
}
Copy the code

Programming instance

Call the interface related to atomic operation and observe the result:

1. Create two tasks

  • Task 1 adds the global variable 100 times with LOS_AtomicAdd.
  • Task 2 subtracted the global variable 100 times using LOS_AtomicSub.

2. After the subtask is complete, print the values of global variables in the main task.

#include "los_hwi.h"
#include "los_atomic.h"
#include "los_task.h"

UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
Atomic g_sum;
Atomic g_count;

UINT32 Example_Atomic01(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicAdd(& g_sum,1);
    }

    LOS_AtomicAdd(& g_count,1);
    return LOS_OK;
}

UINT32 Example_Atomic02(VOID)
{
    int i = 0;
    for(i = 0; i < 100; ++i) {
        LOS_AtomicSub(& g_sum,1);
    }

    LOS_AtomicAdd(& g_count,1);
    return LOS_OK;
}

UINT32 Example_TaskEntry(VOID)
{
    TSK_INIT_PARAM_S stTask1={0};
    stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic01;
    stTask1.pcName       = "TestAtomicTsk1";
    stTask1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask1.usTaskPrio   = 4;
    stTask1.uwResved     = LOS_TASK_STATUS_DETACHED;

    TSK_INIT_PARAM_S stTask2={0};
    stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Atomic02;
    stTask2.pcName       = "TestAtomicTsk2";
    stTask2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    stTask2.usTaskPrio   = 4;
    stTask2.uwResved     = LOS_TASK_STATUS_DETACHED;

    LOS_TaskLock(a);LOS_TaskCreate(& g_testTaskId01, & stTask1);LOS_TaskCreate(& g_testTaskId02, & stTask2);LOS_TaskUnlock(a);while(LOS_AtomicRead(&g_count) ! =2);
    dprintf("g_sum = %d\n", g_sum);return LOS_OK;
}
Copy the code

results

g_sum = 0
Copy the code

Intensive reading of the kernel source code

Four code stores synchronous annotation kernel source code, >> view the Gitee repository

Analysis of 100 blogs. Dig deep into the core

Add comments to hongmeng kernel source code process, sort out the following article. Content based on the source code, often in life scene analogy as much as possible into the kernel knowledge of a scene, with a pictorial sense, easy to understand memory. It’s important to speak in a way that others can understand! The 100 blogs are by no means a bunch of ridiculously difficult concepts being put forward by Baidu. That’s not interesting. More hope to make the kernel become lifelike, feel more intimate. It’s hard, it’s hard, but there’s no turning back. 😛 and code bugs need to be constantly debug, there will be many mistakes and omissions in the article and annotation content, please forgive, but will be repeatedly amended, continuous update. Xx represents the number of modifications, refined, concise and comprehensive, and strive to create high-quality content.

Compile build The fundamental tools Loading operation Process management
Compile environment

The build process

Environment script

Build tools

Designed.the gn application

Ninja ninja

Two-way linked list

Bitmap management

In the stack way

The timer

Atomic operation

Time management

The ELF format

The ELF parsing

Static link

relocation

Process image

Process management

Process concept

Fork

Special process

Process recycling

Signal production

Signal consumption

Shell editor

Shell parsing

Process of communication Memory management Ins and outs Task management
spinlocks

The mutex

Process of communication

A semaphore

Incident control

The message queue

Memory allocation

Memory management

Memory assembly

The memory mapping

Rules of memory

Physical memory

Total directory

Scheduling the story

Main memory slave

The source code comments

Source structure

Static site

The clock task

Task scheduling

Task management

The scheduling queue

Scheduling mechanism

Thread concept

Concurrent parallel

The system calls

Task switching

The file system Hardware architecture
File concept

The file system

The index node

Mount the directory

Root file system

Character device

VFS

File handle

Pipeline file

Compilation basis

Assembly and the cords

Working mode

register

Anomaly over

Assembly summary

Interrupt switch

Interrupt concept

Interrupt management

HongMeng station | into a little bit every day, the original is not easy, welcome to reprint, please indicate the source.