본문 바로가기
IT 기술/리눅스 커널

[C] _Generic keyword

by 땅뚱 2021. 8. 26.

너무나 오랜만에 커널을 다시 보게 되었다. 8년쯤 되었나보다. 그 때는 3.x 의 커널버전이었는데, 이제는 5.x 가 되었다.

소스를 들여다보니, 이런! 커널의 진입장벽은 더 높아졌고, 모르는 내용이 너무 많아서 차근히 정리해보기로 했다.

 

우선 _Generic keyword 부터 시작해보자! (커널은 5.4 를 기준으로 하였다)

이 코드를 보게 된 것은 sequence lock 을 보게 되면서이다. sequence lock (seqlock) 은 이후에 다시 설명하도록 한다.

 

sequence lock 의 read 를 위해서 read_seqcount_begin() 이라는 매크로를 사용하는데, 여기서 코드를 따라가다보면, _Generic keyword 를 만나게 된다. 안녕?

(__seqprop()  에서 _Generic 으로 정의)

<include/linux/seqlock.h>
[...]

#define __seqprop_case(s, lockname, prop)               \
    seqcount_##lockname##_t: __seqprop_##lockname##_##prop((void *)(s))

#define __seqprop(s, prop) _Generic(*(s),               \
    seqcount_t:     __seqprop_##prop((void *)(s)),      \         ---- (5)
    __seqprop_case((s), raw_spinlock,   prop),          \
    __seqprop_case((s), spinlock,   prop),          \
    __seqprop_case((s), rwlock,     prop),          \
    __seqprop_case((s), mutex,      prop),          \
    __seqprop_case((s), ww_mutex,   prop))

#define seqprop_ptr(s)          __seqprop(s, ptr)                  ---- (4)
#define seqprop_sequence(s)     __seqprop(s, sequence)
#define seqprop_preemptible(s)      __seqprop(s, preemptible)
#define seqprop_assert(s)       __seqprop(s, assert)

[...]

/**
 * read_seqcount_begin() - begin a seqcount_t read critical section
 * @s: Pointer to seqcount_t or any of the seqcount_LOCKNAME_t variants
 *
 * Return: count to be passed to read_seqcount_retry()
 */
#define read_seqcount_begin(s)                      \
({                                  \
    seqcount_lockdep_reader_access(seqprop_ptr(s));         \   ---- (3)
    raw_read_seqcount_begin(s);                 \
})

[...]



<include/linux/fs.h>

[...]

static inline loff_t i_size_read(const struct inode *inode)  ---- (1)
{
#if BITS_PER_LONG==32 && defined(CONFIG_SMP)
    loff_t i_size;
    unsigned int seq; 

    do { 
        seq = read_seqcount_begin(&inode->i_size_seqcount);  ---- (2)
        i_size = inode->i_size;
    } while (read_seqcount_retry(&inode->i_size_seqcount, seq));
    return i_size;
#elif BITS_PER_LONG==32 && defined(CONFIG_PREEMPTION)
    loff_t i_size;

    preempt_disable();
    i_size = inode->i_size;
    preempt_enable();
    return i_size;
#else
    return inode->i_size;
#endif
}

[...]

static inline seqcount_t *__seqprop_ptr(seqcount_t *s)   ---- (6)
{
    return s;
}

[...]

 

https://en.cppreference.com/w/c/language/generic

 

Generic selection - cppreference.com

Provides a way to choose one of several expressions at compile time, based on a type of a controlling expression [edit] Syntax _Generic ( controlling-expression , association-list ) (since C11) where association-list is a comma-separated list of associatio

en.cppreference.com

우선 _Generic keword 는 C11 부터 지원했던 c 의 keyword 이다. 사용 문법을 확인해보면, 다음과 같다

 

Syntax

_Generic(controlling-expresssion, association-list)

association-list 는 ',' 로 구분되는 항목이고, 각각은 아래와 같은 문법을 갖는다.
  type-name : expression
  default : expression

모든 expression 은 comma(',')를 제외한 어떤 expression 이 올 수 있다. comma 는 association-list 를 구분하기 위해서 사용하기 때문에 제외된다.

type-name 가변적으로 변하지 않는 완전한 객체 타입
controlling-expression default association 이 사용되지 않는다면, type-name 중 하나와 일치하는 어떤 표현식도 가능
expression 모든 type, 값 카테고리를 나타내는 표현식

2개의 동일한 type 이 association-list 에 올 수 없다. default 는 반드시 한 번만 사용되어야 한다. default 가 사용되지 않는다면, controlling-expression 에는 반드시 type-name 과 일치하는 type 이어야 한다. 그렇지 않다면 컴파일 에러가 발생한다.

 

그럼 위 문법에 기초해서 기존 seqlock.h 에 있던 내용을 다시 분석해보자.

최초 (1) i_size_read() 함수에서 시작된 것이다.

이 함수에서 (2) seq = read_seqcount_begin(&inode->i_size_seqcount); 을 호출한다. i_size_seqcount 필드를 찾아보면 seqcount_t 타입으로 정의되어 있다.

 

따라서 (3) read_seqcount_begin() 의 seqprop_ptr(s) 는 (4)(5) 매크로 정의를 통해서 (6) __seqprop_ptr((void *)(s)) 로 컴파일시 정의된다.

 

커널의 예제가 너무 어려워 보인다면 아래 예제를 보자

https://www.tutorialspoint.com/generic-keyword-in-c-1-20

 

_Generic keyword in C ? 1: 20

_Generic keyword in C ? 1: 20 _Generic keyword in C is used to define MACRO for different data types. This new keyword was added to the C programming language in C11 standard release. the _Generic keyword is used to help the programmer use the MACRO in a m

www.tutorialspoint.com

 

#include <stdio.h>
#define typecheck(T) _Generic( (T), char: 1, int: 2, long: 3, float: 4, default: 0)
int main(void) {
   printf( "passing a long value to the macro, result is %d \n", typecheck(2353463456356465));
   printf( "passing a float value to the macro, result is %d \n", typecheck(4.32f));
   printf( "passing a int value to the macro, result is %d \n", typecheck(324));
   printf( "passing a string value to the macro, result is %d \n", typecheck("Hello"));
   return 0;
}

typecheck(T) 매크로의 파라미터 T 가 char 이면 1 을, int 이면, 2, long 3, float 4 그외는 0 으로 대체해주는 매크로이다.

위 코드를 실행하면 아래와 같다.

 

passing a long value to the macro, result is 3
passing a float value to the macro, result is 4
passing a int value to the macro, result is 2
passing a string value to the macro, result is 0

 

이렇게 _Generic 은 보통 다양한 타입을 처리하기 위한 매크로 정의에서 사용된다. 복잡해보이지만, 하나의 매크로를 사용해서 다양한 타입의 파라미터 처리가 가능하도록 하는 keyword 라고 생각하면 될 것 같다.