Mutex Semaphore (uCOS-II)

ucos-II의 뮤텍스는 우선순위 역전(Priority Inversion) 현상을 해결하기 위해 우선순위 상속(Priority Inheritance)우선순위 실링(Priority Ceiling) 메커니즘을 지원합니다.

특징

  • 한 개의 태스크는 한 개의 우선순위: ucos-II에서는 태스크마다 고유한 우선순위를 가집니다.

  • 우선순위 상속 우선순위(PIP): 뮤텍스를 생성할 때, 해당 뮤텍스를 사용하는 태스크들보다 높은 우선순위 중 사용되지 않는 순위를 PIP(Priority Inheritance Priority)로 지정합니다. 이를 통해 낮은 순위의 태스크가 공유 자원을 점유하고 있을 때, 높은 순위의 태스크가 해당 자원을 기다리면 낮은 순위 태스크의 우선순위를 일시적으로 PIP로 올려 빠른 자원 해제를 유도합니다.

예제 코드:

/* 3개의 태스크(우선순위 10, 15, 20)가 공유 자원에 접근하는 예제.
가장 높은 우선순위(10)보다 높은 9를 PCP(Priority Ceiling Priority)로 예약합니다.
*/
 
ResourceMutex = OSMutexCreate(9, &err); // 우선순위 9를 PIP로 하는 뮤텍스 생성
OSTaskCreate(TaskPrio10, (void *)0, &TaskPrio10Stk[999], 10);
OSTaskCreate(TaskPrio15, (void *)0, &TaskPrio15Stk[999], 15);
OSTaskCreate(TaskPrio20, (void *)0, &TaskPrio20Stk[999], 20);

OSMutexCreate()

뮤텍스를 생성하고 초기화하는 함수입니다.

  1. ISR 호출 여부 확인: ISR(Interrupt Service Routine) 내에서는 호출할 수 없습니다.

  2. PIP 유효 범위 확인: 파라미터로 받은 prio (PIP)가 유효한 범위 내에 있는지 확인합니다.

  3. PIP 사용 여부 확인: 해당 우선순위(prio)가 다른 태스크에 의해 사용되고 있지 않은지 OSTCBPrioTbl[]을 통해 확인합니다.

  4. 우선순위 예약: OSTCBPrioTbl[prio]에 특정 포인터 값을 할당하여 해당 우선순위를 예약합니다.

  5. ECB 할당: EventFreeList에서 사용 가능한 ECB(Event Control Block)를 가져옵니다.

  6. ECB 초기화:

    • ECB 타입을 OS_EVENT_TYPE_MUTEX로 설정합니다.

    • 뮤텍스를 ‘사용 가능’ 상태로 설정하고 PIP를 저장합니다.

    • .OSEventCnt 필드의 상위 8비트에는 PIP를, 하위 8비트에는 뮤텍스 값(사용 가능 시 0xFF)을 저장하여 메모리를 절약합니다.

  7. 대기 리스트 초기화: OSEventWaitListInit()를 호출하여 대기 중인 태스크 리스트를 초기화합니다.

  8. ECB 포인터 반환: 생성된 뮤텍스의 핸들(ECB 포인터)을 반환합니다.

Figure 8.2, ECB just before OSMutexCreate() returns

OSMutexDel()

생성된 뮤텍스를 삭제하는 함수입니다.

  1. ISR 호출 및 인수 유효성 검사: ISR에서 호출되었는지, 인수가 유효한지 확인합니다.

  2. 대기 작업 확인: 뮤텍스를 기다리는 태스크가 있는지 확인합니다.

  3. 삭제 옵션 처리:

    • OS_DEL_NO_PEND: 대기 중인 태스크가 없을 때만 뮤텍스를 삭제합니다.

    • OS_DEL_ALWAYS: 대기 중인 태스크가 있어도 뮤텍스를 삭제하고, 대기 중인 모든 태스크를 준비(Ready) 상태로 만듭니다. (주의: 자원 보호가 깨질 수 있음)

  4. ECB 반환: ECB를 Free ECB List로 반환합니다.

  5. 스케줄러 호출: 대기 중인 태스크가 있었다면 스케줄러를 호출하여 CPU를 재할당합니다.

  6. NULL 반환: 삭제 후에는 유효하지 않은 포인터를 참조하지 않도록 NULL을 반환합니다.

OSMutexPend()

뮤텍스를 얻기 위해 대기하는 함수입니다.

  1. ISR 호출 및 인수 유효성 검사: ISR 호출 여부와 인수의 유효성을 확인합니다.

  2. 뮤텍스 상태 확인:

    • 사용 가능: 뮤텍스를 즉시 획득하고, .OSEventCnt 하위 8비트에 현재 태스크의 우선순위를 기록한 후 OS_NO_ERR를 반환합니다.

    • 사용 불가: 다른 태스크가 소유 중이면 대기 상태로 전환합니다.

  3. 우선순위 상속 처리: 만약 현재 태스크의 우선순위가 뮤텍스를 소유한 태스크보다 높다면, 소유한 태스크의 우선순위를 PIP로 상향 조정하여 우선순위 역전 문제를 해결합니다.

  4. 대기 상태 전환: timeout 값에 따라 지정된 시간만큼 또는 무한히 대기 상태로 전환하고, 스케줄러를 호출하여 다른 태스크를 실행합니다.

  5. 대기 해제: 뮤텍스가 해제되거나 타임아웃이 발생하면, 태스크는 다시 준비 상태가 됩니다. 타임아웃 시 에러 코드를 반환합니다.

OSMutexPost()

소유하고 있던 뮤텍스를 해제하는 함수입니다.

  1. ISR 호출 및 인수 유효성 검사: ISR 호출 여부와 인수의 유효성을 확인합니다.

  2. 소유권 확인: 함수를 호출한 태스크가 실제로 뮤텍스의 소유자인지 확인합니다.

  3. 우선순위 복원: 만약 우선순위 상속으로 인해 우선순위가 변경되었다면, 원래의 우선순위로 복원합니다.

  4. 대기 태스크 확인:

    • 대기 태스크 있음: 대기 중인 태스크 중 가장 우선순위가 높은 태스크를 준비 상태로 만들고, 이 태스크가 새로운 뮤텍스 소유자가 됩니다.

    • 대기 태스크 없음: 뮤텍스를 ‘사용 가능’ 상태(0xFF)로 변경합니다.

  5. 스케줄러 호출: 새로운 준비 상태가 된 태스크가 현재 태스크보다 우선순위가 높으면 컨텍스트 스위칭이 발생합니다.


Atmel AVR - Interrupt

인터럽트란 CPU가 특정 작업을 수행하는 도중 긴급하게 처리해야 할 다른 이벤트가 발생했을 때 실행 흐름을 전환하는 메커니즘입니다.

트리거 방식

  • Edge Trigger: 입력 신호의 상태가 변하는 순간(상승 에지 또는 하강 에지)에 인터럽트를 발생시킵니다.

  • Level Trigger: 입력 신호가 특정 레벨(High 또는 Low)을 유지하는 동안 인터럽트를 발생시킵니다.

인터럽트 설정 3단계

  1. 전체 인터럽트 허용: SREG 레지스터의 전역 인터럽트 허용 비트(I-bit)를 1로 설정합니다. sei() 함수를 사용합니다.

  2. 개별 인터럽트 허용: EIMSK 레지스터에서 사용하려는 특정 외부 인터럽트 비트(예: INT4)를 1로 설정합니다.

  3. 트리거 방식 설정: EICRA 또는 EICRB 레지스터에서 원하는 인터럽트의 트리거 방식(ex: 하강 에지)을 설정합니다.

주요 레지스터

  • SREG: 상태 레지스터. 7번 비트(I)가 전역 인터럽트를 제어합니다. (sei(): set, cli(): clear)

  • EIMSK: 외부 인터럽트 마스크 레지스터. INT0~INT7의 허용 여부를 개별적으로 제어합니다.

  • EICRA/EICRB: 외부 인터럽트 제어 레지스터. 각 인터럽트의 트리거 방식을 설정합니다.

ISCn1ISCn0기능
00Low Level
01(예약됨)
10하강 에지
11상승 에지

ISR (Interrupt Service Routine) 작성

#include <avr/interrupt.h>
 
// INT4 인터럽트 서비스 루틴
ISR(INT4_vect) {
    // 인터럽트 발생 시 실행할 코드
}
 
int main(void) {
    // INT4를 하강 에지 방식으로 설정
    EICRB = 0x02; // 0b00000010
 
    // INT4 인터럽트만 활성화
    EIMSK = 0x10; // 0b00010000
 
    // 전역 인터럽트 활성화
    sei(); // 또는 SREG |= (1 << 7);
 
    while(1) {
        // 메인 루프
    }
}

Atmel AVR - Buzzer & Timer/Counter

Buzzer

부저(Buzzer)는 전기 신호를 소리로 변환하는 장치입니다. 특정 주파수의 사각파(Square Wave) 신호를 인가하여 원하는 음을 낼 수 있습니다.

예를 들어, ‘도’ 음(1046.5Hz)을 내고 싶다면 주기가 약 955µs이므로, 478µs 동안 High 신호, 477µs 동안 Low 신호를 반복적으로 출력하면 됩니다. 이러한 정밀한 시간 제어는 타이머/카운터를 통해 구현합니다.

Timer/Counter

타이머/카운터는 MCU 내부 또는 외부 클럭을 세어 정해진 시간에 도달하면 인터럽트를 발생시키는 장치입니다.

  • Timer: MCU의 내부 시스템 클럭을 셉니다.

  • Counter: 외부 핀으로 입력되는 클럭(펄스)을 셉니다.

ATmega128의 타이머/카운터

  • 종류: Timer0/2 (8비트), Timer1/3 (16비트)

  • 주요 기능:

    • 오버플로우 인터럽트: 카운터 값이 최대치(예: 8비트의 경우 255)를 넘어 다시 0이 될 때 발생합니다.

    • 출력 비교(Compare Match) 인터럽트: 카운터 값이 특정 레지스터(OCRn)의 값과 일치할 때 발생합니다.

프리스케일러 (Prescaler)

MCU의 클럭(예: 16MHz)은 매우 빠르기 때문에 긴 시간을 측정하기 어렵습니다. 프리스케일러는 이 클럭을 1, 8, 64, 256, 1024 등의 비율로 나누어(분주하여) 타이머가 세는 클럭의 주기를 늘려줍니다. 이를 통해 더 긴 시간을 정밀하게 측정할 수 있습니다.

  • 프리스케일러 설정 (TCCRn 레지스터)
CSn2CSn1CSn0분주비 (Prescaler)
0011
0108
01164
100256
1011024

시간 지연 계산 예시

  • 조건: 16MHz 클럭, 64분주 프리스케일러, 8비트 타이머, 100µs 지연
  1. 타이머 1틱(tick) 시간 계산:

    (1/16,000,000 Hz)×64=4μs

  2. 필요한 틱 수 계산:

    100μs/4μs=25 ticks

  3. TCNT0 초기값 설정: 오버플로우는 255에서 0으로 넘어갈 때 발생하므로, 25번만 세고 오버플로우가 발생하도록 초기값을 설정합니다.

    256−25=231

    따라서 TCNT0 레지스터에 231을 설정하고 오버플로우 인터럽트를 활성화하면 정확히 100µs 후에 인터럽트가 발생합니다.

주요 레지스터

  • TCCRn: 타이머 제어 레지스터 (프리스케일러, 동작 모드 설정)

  • TCNTn: 현재 카운터 값을 저장하는 레지스터

  • TIMSK: 타이머 인터럽트 마스크 레지스터 (오버플로우, 출력 비교 인터럽트 활성화)


Atmel AVR - CDS & ADC

CDS (조도 센서)

CDS는 빛의 양에 따라 저항값이 변하는 센서로, 아날로그 값을 출력합니다. 이 값을 MCU가 처리하기 위해서는 ADC를 사용해야 합니다.

ADC (Analog to Digital Converter)

ADC는 아날로그 신호를 디지털 값으로 변환하는 장치입니다.

  • 분해능(Resolution): ADC가 표현할 수 있는 최소 아날로그 전압의 크기. N비트 ADC의 경우, 기준 전압을 2N 단계로 나눕니다.

  • 변환 시간(Conversion Time): 아날로그-디지털 변환에 걸리는 시간. 이의 역수가 샘플링 속도(Sampling Rate)입니다.

  • 주요 레지스터:

    • ADMUX: 아날로그 채널 선택(멀티플렉서), 기준 전압 선택.

    • ADCSRA: ADC 제어 및 상태 레지스터 (ADC 활성화, 변환 시작, 인터럽트 설정).

    • ADCH, ADCL: 변환된 디지털 데이터가 저장되는 레지스터.


I2C (Inter-Integrated Circuit)

I2C는 필립스에서 개발한 동기식 직렬 통신 프로토콜로, 적은 수의 선으로 여러 장치를 연결할 수 있습니다.

  • 통신 라인
라인 이름설명
SDA (Serial Data)데이터 송수신 라인 (양방향)
SCL (Serial Clock)데이터 동기화를 위한 클록 라인 (마스터가 생성)
  • 주요 특징:

    • 마스터-슬레이브 구조: 통신을 주도하는 마스터와 이에 응답하는 슬레이브로 구성됩니다.

    • 멀티 마스터 지원: 여러 개의 마스터 장치가 하나의 버스를 공유할 수 있습니다.

    • 주소 지정: 각 슬레이브는 7비트 또는 10비트의 고유 주소를 가집니다.

    • 간단한 연결: 단 2개의 신호선으로 통신이 가능합니다.


CPU 성능 지표

MIPS (Million Instructions Per Second)

CPU가 1초에 몇 백만 개의 명령어를 처리할 수 있는지를 나타내는 성능 지표입니다.

  • Clock Cycle Time (tc​): 한 클럭 사이클에 걸리는 시간. 클럭 속도(rc​)의 역수입니다.

    tc​=1/rc​

  • CPU 실행 시간 (tcpu​): 프로그램 실행에 필요한 총 CPU 시간.

    tcpu​=(Instruction Count)×(CPI)×(Clock Cycle Time)

    • Instruction Count: 프로그램이 실행하는 총 명령어 수

    • CPI (Cycles Per Instruction): 명령어 하나를 처리하는 데 필요한 평균 클럭 사이클 수


ARM ISA (Instruction Set Architecture)

ARM은 대표적인 RISC(Reduced Instruction Set Computer) 아키텍처입니다.

주요 특징

  • Load/Store 구조: 메모리 접근은 LoadStore 명령어로만 가능하며, 나머지 데이터 처리 명령어는 레지스터 간의 연산만 수행합니다.

  • 많은 범용 레지스터: 32비트 균일한 크기의 범용 레지스터를 다수 보유하고 있습니다.

  • 간단한 주소 지정 방식(Addressing Mode): 주소는 레지스터 값이나 명령어 필드에서 직접 결정됩니다.

  • 고정 길이 명령어: 모든 명령어는 32비트(또는 16비트 Thumb)로 길이가 고정되어 있어 디코딩이 간단합니다.

  • 파이프라이닝(Pipelining): 명령어를 여러 단계(Fetch, Decode, Execute, Memory, Write-back)로 나누어 동시에 처리하여 명령어 처리량(Throughput)을 높입니다.

    • 단점: 데이터 해저드, 제어 해저드, 구조적 해저드와 같은 문제가 발생할 수 있습니다.

ARM 명령어 형식 (32비트)

비트31-2827-252423-212019-1615-1211-0
필드CondTypeIOpcodeSRnRdOperand2
설명조건 코드종류즉시값연산자플래그 설정피연산자1결과 레지스터피연산자2
  • Cond 필드와 조건부 실행: 대부분의 ARM 명령어는 Cond 필드를 통해 조건부로 실행될 수 있습니다. 이를 통해 불필요한 분기(Branch) 명령어를 줄여 파이프라인 효율을 높일 수 있습니다.

    ARM assembler

    CMP R1, R2      ; R1과 R2를 비교 (R1 - R2)
    ADDEQ R3, R4, R5  ; 만약 R1 == R2 (EQ) 이면, R3 = R4 + R5를 실행
    

ARM Branch (분기) 명령어

프로그램의 실행 흐름을 특정 주소로 변경하는 명령어입니다.

필드31-2827-2423-0
설명Cond (조건 코드)OpcodeOffset (주소 오프셋)
  • B <Label> (Branch): 지정된 라벨로 무조건 분기합니다. PC 값을 변경합니다.

  • BL <Label> (Branch with Link): 함수 호출에 사용됩니다. 복귀할 주소(PC + 4)를 **LR(Link Register, R14)**에 저장한 후, 지정된 라벨로 분기합니다. 함수 종료 시 MOV PC, LR 명령어로 복귀합니다.

  • 조건부 분기: B와 조건 코드를 조합하여 특정 조건이 만족될 때만 분기합니다. (예: BEQ, BNE, BGT)

분기 주소 계산

ARM 명령어는 4바이트 단위로 정렬되므로, 명령어의 주소는 항상 4의 배수입니다. 따라서 주소의 하위 2비트는 항상 ‘00’입니다. Branch 명령어의 24비트 Offset 필드에 이 2비트를 저장할 필요가 없으므로, 실제 주소 계산 시에는 Offset 값을 **왼쪽으로 2비트 시프트(<< 2)**하여 4를 곱한 효과를 냅니다.

Target Address=(PC+8)+(Offset≪2)

  • PC + 8: ARM의 3단계 파이프라인 때문에 PC는 현재 실행 중인 명령어보다 2단계 앞선 주소를 가리킵니다.

ARM 상태 레지스터 명령어 (MRS / MSR)

CPSR(현재 프로그램 상태 레지스터)과 SPSR(저장된 프로그램 상태 레지스터)에 접근하기 위한 명령어입니다.

  • MRS Rd, CPSR (Move from Status Register): CPSR의 값을 범용 레지스터 Rd로 복사합니다. 현재 CPU 상태(플래그, 모드 등)를 확인할 때 사용합니다.

  • MSR CPSR_c, Rm (Move to Status Register): 범용 레지스터 Rm의 값을 CPSR의 특정 필드(예: _c는 조건 플래그)에 씁니다. CPU 모드를 변경하거나 인터럽트를 비활성화할 때 사용합니다.

사용 예시 (IRQ/FIQ 비활성화):

ARM assembler

MRS R0, CPSR         ; 현재 CPSR 값을 R0에 읽어옴
ORR R0, R0, #0xC0    ; IRQ(bit 7)와 FIQ(bit 6) 비트를 1로 설정
MSR CPSR_c, R0       ; 변경된 값을 CPSR에 씀

주의: CPSR의 모든 필드를 수정하는 것은 특권 모드에서만 가능합니다.


ARM 상태 레지스터 (CPSR / SPSR)

  • CPSR (Current Program Status Register): 현재 프로그램의 상태 정보를 담고 있는 32비트 레지스터입니다.
비트313029287654-0
필드NZCVIFTMode
설명NegativeZeroCarryOverflowIRQ DisableFIQ DisableThumbCPU 모드
  • SPSR (Saved Program Status Register): 예외(Exception)나 인터럽트가 발생했을 때, 그 직전의 CPSR 값을 백업하는 레지스터입니다. 예외 처리가 끝난 후 SPSR의 값을 CPSR로 복원하여 원래 실행 흐름으로 돌아갑니다. 특권 모드에서만 접근 가능합니다.

ARM 블록 전송 명령어 (LDM / STM)

여러 개의 레지스터 값을 메모리와 한 번에 주고받는 명령어입니다. 함수 호출 시 컨텍스트 스위칭(레지스터 저장/복원)이나 데이터 블록 복사에 매우 효율적입니다.

  • STM (Store Multiple): 여러 레지스터의 값을 메모리에 저장합니다.

  • LDM (Load Multiple): 메모리에서 여러 레지스터로 값을 로드합니다.

함수 호출에서의 사용 (스택 관리)

함수 프롤로그(시작 부분)와 에필로그(끝 부분)에서 스택 포인터(SP)를 이용해 레지스터를 저장하고 복원하는 데 주로 사용됩니다.

ARM assembler

; 함수 프롤로그: R4-R7과 복귀 주소(LR)를 스택에 저장
STMFD SP!, {R4-R7, LR}

; ... 함수 본문 ...

; 함수 에필로그: 스택에서 R4-R7을 복원하고, 저장했던 LR 값을 PC에 넣어 복귀
LDMFD SP!, {R4-R7, PC}
  • FD: Full Descending Stack을 의미. 스택 포인터가 낮은 주소로 감소하며 쌓이는 방식.

  • !: Write-back. 연산 후 변경된 주소(SP)를 레지스터에 다시 씀.


ARM 프로그래머 모델 (레지스터)

범용 레지스터

레지스터별칭주 용도
R0 - R3-함수 인자 전달 및 반환 값
R4 - R11-지역 변수 저장
R12IP프로시저 간 스크래치 레지스터
R13SP스택 포인터 (Stack Pointer)
R14LR링크 레지스터 (Link Register)
R15PC프로그램 카운터 (Program Counter)

특수 레지스터

  • R13 (SP): 스택의 최상단을 가리키는 포인터. 데이터 push/pop 시 주소 값이 변경됩니다.

  • R14 (LR): 함수 호출(BL) 시 복귀할 주소를 저장합니다. 예외 발생 시에도 복귀 주소를 저장하는 용도로 사용됩니다.

  • R15 (PC): 현재 실행 중인 명령어의 주소를 가리킵니다. (정확히는 파이프라인 때문에 현재 명령어 주소 + 8)

뱅크드 레지스터 (Banked Registers)

ARM은 프로세서 모드에 따라 일부 레지스터가 물리적으로 분리되어 있습니다. 예를 들어, IRQ 모드로 진입하면 사용자 모드의 SP, LR 대신 IRQ 모드 전용의 SP_irq, LR_irq를 사용합니다. 이를 통해 모드 전환 시 원래 레지스터 값을 덮어쓸 위험 없이 안전하게 상태를 보존할 수 있습니다. 특히 FIQ 모드는 R8-R12까지 뱅크드 되어 더 빠른 컨텍스트 스위칭을 지원합니다.


ARM 프로세서 모드

ARM 프로세서는 실행 권한과 목적에 따라 여러 동작 모드를 가집니다.

모드설명주 용도
User (usr)비특권 모드일반 애플리케이션 실행
System (sys)특권 모드운영체제 작업 (사용자 레지스터 사용)
Supervisor (svc)특권 모드리셋, SWI(소프트웨어 인터럽트) 처리
Abort (abt)특권 모드메모리 접근 실패 예외 처리
Undefined (und)특권 모드정의되지 않은 명령어 예외 처리
IRQ (irq)특권 모드일반 인터럽트 처리
FIQ (fiq)특권 모드고속 인터럽트 처리

ARM 예외 처리 순서

예외(인터럽트, 메모리 오류 등)가 발생하면 ARM 코어는 하드웨어적으로 다음 순서에 따라 동작합니다.

  1. CPSR 백업: 현재 CPSR의 값을 예외 모드에 해당하는 SPSR에 복사합니다.

  2. 복귀 주소 저장: PC 값을 예외 모드의 LR에 저장합니다.

  3. 모드 변경: CPSR의 모드 비트를 변경하여 해당 예외 처리 모드(예: IRQ 모드)로 전환합니다.

  4. PC 변경: 예외 벡터 테이블에서 해당 예외의 주소를 찾아 PC에 로드합니다.

  5. 예외 핸들러 실행: PC가 가리키는 주소의 예외 처리 루틴(핸들러)을 실행 시작합니다.


리눅스 커널: struct file_operations

디바이스 드라이버가 파일 시스템을 통해 사용자 공간과 상호작용하는 방법을 정의하는 함수 포인터 구조체입니다. open, read, write 같은 시스템 콜이 호출될 때, 커널은 이 구조체에 등록된 해당 함수를 호출합니다.

  • .open, .release: 디바이스 파일을 열고 닫을 때 호출됩니다.

  • .read, .write: 디바이스로부터 데이터를 읽거나 쓸 때 호출됩니다.

  • .unlocked_ioctl: 디바이스에 특화된 제어 명령을 처리할 때 호출됩니다.

  • .mmap: 디바이스 메모리를 사용자 공간에 직접 매핑할 때 사용됩니다.

  • .poll: 디바이스가 비동기 I/O를 지원할 때, 데이터 준비 상태를 확인하기 위해 호출됩니다.


리눅스 커널: User ↔ Kernel 데이터 전송

보안상의 이유로 커널 공간은 사용자 공간 메모리에 직접 접근할 수 없습니다. 따라서 데이터를 안전하게 복사하기 위해 전용 함수를 사용해야 합니다.

  • copy_from_user() / copy_to_user()

    • 용도: **메모리 블록(버퍼, 구조체, 배열)**을 복사할 때 사용합니다.

    • 동작: 지정된 크기만큼의 데이터를 사용자 공간과 커널 공간 사이에서 복사합니다.

  • get_user() / put_user()

    • 용도: **단일 변수(int, char 등 스칼라 값)**를 주고받을 때 사용합니다.

    • 동작: copy_..._user보다 오버헤드가 적어 간단한 값을 주고받기에 더 효율적입니다.

결론: 여러 바이트로 구성된 데이터는 copy_..._user를, 정수나 문자 하나와 같은 작은 데이터는 get/put_user를 사용하는 것이 좋습니다.