https://www.kernel.org/doc/html/latest/process/coding-style.html
Linux kernel coding style — The Linux Kernel documentation
Linux kernel coding style This is a short document describing the preferred coding style for the linux kernel. Coding style is very personal, and I won’t force my views on anybody, but this is what goes for anything that I have to be able to maintain, an
www.kernel.org
이 문서는 linux kernel을 위한 선호되는 coding style을 설명하는 짧은 문서이다. Coding style은 매우 개인적이며 누구에게든 나의 관점을 강요하고 싶지는 않지만 유지보수를 가능하게 하기 위해 따르기를 바라며, 대부분 다른 것에서도 이것을 선호한다. 여기에 언급된 최소한 고려하자.
먼저 GNU coding standard를 출력하고 읽지 말고 불태우면, 훌륭한 symbolic gesture이다.
Anyway, here goes:
1) Indentation
Tab는 8 characters이며 indentation 또한 8 characters이다. indentation를 4 (또는 심지어 2!) character deep으로 하려고 하는 이단의(heretic) 움직임이 있는데, 이것은 PI를 3으로 정의하려고 하는 것과 비슷하다.
근거(Rationale): identation의 전체 idea는 control block의 시작과 끝을 명확하게 정의하는 것이다. 특히 20 시간 연속 screen을 들여다보면 큰 identation일 경우 indentation이 어떻게 동작하는지 더 쉽게 발견할 것이다.
이제, 몇몇 사람들이 8-character indentations을 가지는 것은 code를 너무 오르쪽으로 멀리 보낸다는 것을 주장할 것인데, 80-character terminal screen을 읽는 것은 어렵다. 이러한 답변은 만약 3 level 이상의 identation을 필요로 한다면, 어차피 망한 것이므로 프로그램을 수정해야만 한다.
요약하면, 8-char idents는 읽기 쉽게 하며 당신의 function이 너무 깊을 때 경고하는 이점을 추가한다. 그 경고에 주의하자.
switch statement에서 많은 indentation을 쉽게하는 쉬운 방식은 case labels을 double-indenting 하는 것 대신 switch와 종속자(subordinate) case label을 같은 column에 정렬하는 것이다. E.g.:
switch (suffix) {
case 'G':
case 'g':
mem <<= 30;
break;
case 'M':
case 'm':
mem <<= 20;
break;
case 'K':
case 'k':
mem <<= 10;
fallthrough;
default:
break;
}
숨기기 위한 무엇인 가를 가지지 않는다면, multiple statements을 한 라인에 두지 않는다:
if (condition) do_this;
do_something_everytime;
braces를 피하기 위해 commas를 사용하지 않는다:
if (condition)
do_this(), do_that();
항상 multiple statements를 위한 braces를 사용한다.
if (condition) {
do_this();
do_that();
}
한 라인에 multiple assignment를 두지 않는다. Kernel coding style은 매우 단순하다. 까다로운 표현은 피한다.
Kconfig를 제외하고 comment, documents 외부에서 spces는 indetation으로 절대 사용하지 않으며 위 예시는 의도적으로 깨진 것이다.
좋은 editor를 구해 lines의 끝에 whitespace를 남겨두지 않는다.
2) Breaking long lines and strings
Coding style은 공통적으로 사용 가능한 tools를 사용하는 가독성과 유지보수성에 관한 모든 것이다.
한 line의 길에서 선호되는 제한은 80 columns이다.
80 colums 이상의 statements는 합리적인 chunk로 분리되어야한다. 하지만 80 columns 를 초과하는 것이 가독성을 크게 증가시키며 정보를 숨기지 않지 않으면 가능하다.
원래 라인(parent)보다 후속 라인(descendants)은 항상 짧으며 오른쪽에 배치된다. 매우 일반적으로 사용되는 style은 open parenthesis('(')에 후속 라인(descendants)을 정렬하는 것이다.
같은 rule이 긴 arguemnt list를 가진 function headers에도 적용된다.
하지만, 절대 prink message 같은 user-visible strings을 깨뜨리지 않는다. 왜냐하면 이것은 grep 시 찾기 어렵게 하기 때문이다.
3) Placing Braces and Spaces
C styling에서 항상 동반되는 다른 이슈는 brace의 위치이다. indent size와는 달리, one placement strategy를 선택하는 기술적인 이유가 없지만 prophets Kernighan과 Ritchie에 의해 보여진 선호되는 방식은 line의 끝에 opening brace를 두고, line의 처음에 closing brace를 두는 것이다:
if (x is ture) {
we do y
}
모든 non-function statement blocks (if, switch, for, while, do)에 적용된다. E.g.:
switch (action) {
case KOBJ_ADD:
return "add";
case KOBJ_REMOVE:
return "remove";
case KOBJ_CHANGE:
return "change";
default:
return NULL;
}
하지만, 하나 특별한 경우, 즉 functions: 다음 라인의 처음에 opening brace를 가진다:
int function(int x)
{
body of function
}
전 세계의 이단자(Heretic people)들은 이것이 일관적이지 않다고 주장해왔지만, 올바른 생각을 가진 모든 사람들은 (a) K&R이 right하며 (b) K&R이 right 생각한다. 게다가 functions은 어쨋든 특별하다(C에 이들을 포함시킬 수 없다).
closing brace는 같은 statement가 연속으로 이어지는 경우를 제외하면 다음 과 같이 한줄 비어둔다. i.e., do-statement에서 while 또는 if-statement에서 else:
do {
body of do-loop
} while (condition);
and
if (x == y) {
...
} else if (x > y) {
...
} else {
...
}
근거(Rationale): K&R
또한, 이러한 brace-placement는 가독성 저하 없이 또한 빈 라인 (또는 거의 빈 라인)을 최소화한다. 그러므로, 새로운-라인의 공급은 재생 가능한 리소스가 아니기 때문에 (25-line terminal screens을 생각해보자), 주석을 달 수 있는 더 많은 빈 라인을 가진다.
하나의 statement가 수행하는 위치에 불필요한 brace를 사용하지 않는다.
if (condition)
action();
and
if (condition)
do_this();
else
do_that();
만약 conditional statement의 하나의 branch가 single statement 라면 이것을 적용하지 않는다; 후자의 경우 두 branch에서 braces를 사용한다.
if (condition) {
do_this();
do_that();
} else {
otherwise();
}
또한 loop가 하나의 단순한 statement보다 더 많은 것을 포함할 때 brace를 사용한다.
while (condition) {
if (test)
do_something();
}
3.1) Spaces
space의 사용을 위한 Linux kernel style은 function-versus-keyword 사용에 의존한다. (대부분) keyword 이후 space를 사용한다. 주목할만한 예외는 sizeof, typeof, alignof, __attribute__이며 마치 function처럼 보인다(그리고 주로 Linux에서는 parentheses와 함께 사용되며, 이들이 lanaguage에서 필수적이지 않음에도 불구하고, struct fileinfo info; 이후 sizeof info;로 선언됨).
이러한 keyword 이후에는 space를 사용한다:
if, switch, case, for, do, while
sizeof, typeof, alignof, __attribute__ 에는 사용하지 않는다. E.g.,
s = sizeof(struct file);
parenthesized expression 주위에 space를 추가하지 않는다. 다음 예제는 나쁘다.
s = sizeof( struct file );
pointer data 또는 pointer type을 return하는 function을 정의할 때 선호되는 *의 사용은 data name 또는 function name 근처이며 type name 근처가 아니다.
예시:
char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);
다음과 같은 대부분의 binary 그리고 ternary oprators 주위에 한 칸을 사용한다:
= + - < > * / % | & ^ <= >= == != ? :
단항(unary) operator 이후 space는 없다:
& * + - ~ ! sizeof typeof alignof __attribute__ defined
postfix increment & decrement unary operator 전에 space는 없다:
++ --
prefix increment & decrement unary operator 이후 space는 없다:
++ --
그리고 . 과 -> structure member operator 주위에 space는 없다.
line의 끝에 whitespace를 남기지 않는다. smart indentation을 가진 몇몇 editor에는 새로운 line 시작 시 whitespace를 적절히 삽입하여 새로운 line에서 code를 올바르게 작성할 수 있다. 하지만, 이러한 editors는 만약 blank line을 남겨두는 것처럼 code의 line을 넣지 않으면 whitespace를 제거하지 않는다. 그 결과 whitespace를 포함하는 line으로 끝나게 된다.
Git은 trailing whitespace를 도입하는 패치를 경고할 것이며 선택적으로 trailing whitespace를 strip 할 수 있다; 하지만, 만약 이러한 patch를 적용한다면, 이후 patch가 context line을 변경하여 실패할 수도 있다.
4) Naming
C는 검소한(Spartan) language이며 naming conventions을 따라야만 한다. Modula-2와 Pascal programmers와는 달리, C programmers는 ThisVariableATemporaryCounter 같은 이름을 사용하지 않는다. C programmer는 tmp 같은 variable을 사용하며 작성하기 훨씬 더 쉽고 이해하기 어렵지 않은 변수를 사용한다.
하지만, mixed-case 이름은 눈쌀을 찌푸리게 하지만, global variables에 대한 설명적인 이름은 필수다. global function를 foo로 부르는 것은 총살감(shooting offense)이다.
GLOBAL variables (정말 필요할 때만 사용)과 global functions은 설명적인 이름을 가질 필요가 있다. 만약 active users의 수를 카운트하는 function이라면 count_active_user() 또는 비슷한 이름이어야만 하며 cntusr()로 하면 안된다.
name으로 function의 type을 encoding 하는 것은 어리석은 짓이다(함수 이름에 타입을 명시하는 것) - compiler는 type을 어떤 방식이로든 알 수 있으며 확인할 수 있으며 programmer를 혼란스럽게만 한다.
LOCAL variable names은 짧아야만 하며 핵심을 나타내야한다. 만약 random integer loop counter이라면, i로 부르는 것이 적절하다. 헷갈릴 여지가 없다면 loop_counter로 부르는 것은 비생산적이다. 비슷하게도, tmp는 임시 값을 유지하는데 사용되는 변수의 어떤 타입이든 사용할 수 있다.
만약 local variable names이 헷갈릴까 걱정된다면, 다른 문제가 있는데 그것은 function-growth-hormone-imbalance syndrome 이다. chapter 6(Functions)를 확인한다.
symbol names과 documentation의 경우, 'master/slave', 'blacklist/whitelist'의 새로운 사용을 피한다.
'master/slave'의 추천되는 대체 용어:
- primary,main}/{secondary,replica,subordinate}'
- '{initator,requester}/{target,reponder}'
- '{controller,host}/{device,worker,proxy}'
- '{leader/follower''director/performer'
'blacklist/whitelist'의 추천되는 대체 용어
- denylist/allowlist
- blocklist/passlist
새로운 사용에 관한 예외는 userspace ABI/API를 유지하는 경우 또는 (2020 기준) 기존 hardware 또는 protocol specification에서 해당 용어를 강제하는 경우이다. 새로운 specification의 경우 가능하다면 kerenl coding standard에 맞게 변경한다.
5) Typedefs
vps_t 같은 것을 사용하지 말자. structure와 pointers의 typedef 사용하는 것은 실수이다.
다음과 같은 것을 볼 경우
vps_t a;
무엇을 의미할까? 반면 만약 다음과 같을 때
struct virtual_container *a;
실제 a가 무엇인지 알 수 있다.
많은 사람들은 typedefs가 가독성을 도와준다고 생각한다. 사실이 아니다. 다음과 같을 경우에만 유용하다:
- 전체적으로 불투명한 object(typedef는 object가 무엇인지 숨기는데 사용된다).
Example: pte_t etc. 적절한 accessor function을 사용해 접근될 수 있는 불투명한 object.
불투명성과 accessor functions는 그 자체로 좋지 않다. pte_t 같은 것들에 이러한 기능을 제공하는 이유는 portable하게 접근할 수 있는 정보가 없기 때문이다. - 명확한 integer types, 추상화는 int 또는 long인지 혼동을 피하는 데 도움이 된다.
u8/u16/u32는 완벽하게 좋은 typedefs이며 여기보다는 category (d)에 더 잘 맞는다.
하지만 만약 unsigned int 일 수 있는 특정 상황과 다른 설정이 unsigned long일 수 있는 상황인 명확한 이유가 있다면, typedef를 사용한다.Note: 다시 말하지만 - 여기에는 이유가 필요하다. 만약 unsigned long이라면 그럴 이유가 없다. typedef unsigned long myflags_t; - 문자 그대로 type-checking을 위해 새로운 type을 생성하기 위해 sparse(정적분석기)를 사용할 때
- standard C99 types와 동일한 새로운 type, 특정 예외 상황.
uint32_t 같은 standard type에 익숙해지는 데 짧은 시간밖에 걸리지 않지만, 몇몇 사람들은 이들 사용을 반대한다.
그러므로, standard type과 같은 Linux-specific u8/u16/u32/u64 types와 이들의 signed 버전은 허용되지만 새로운 코드에서는 필수는 아니다.
위 타입 또는 다른 type을 이미 사용하는 기존 코드를 수정할 때, 코드의 기존 선택을 수용해야만 한다. - userspace에서 안전하게 사용하는 types
userspace에서 볼 수 있는 특정 structures에서, C99 type을 필요로 하지 않을 수 있고 u32 형태를 사용하지 않을 수 있다. 그러므로, userspace로 공유되는 모든 structures에서 __u32 와 비슷한 types을 사용한다.
다른 경우도 있을 것이지만 rule은 기본적으로 만약 위 rule 중 하나와 명확히 일치하지 않는다면 절대로 typedef를 사용하지 않아야만 한다.
일반적으로, pointer 또는 합리적으로 직접 접근할 수 있는 element를 가지는 struct는 절대 typedef가 되서는 안된다.
6) Functions
Functions은 짧고 sweet해야하며 한가지만 수행해야만 한다. 이들은 one or two screenfuls of text (ISO/ANSI screen size는 80x24, 우리 모두 알고 있듯)에 fit해야만 하며, 한 가지만 수행하고 잘 수행해야한다.
function의 최대 길이는 복잡성과 indentation 수준에 반비례한다. 따라서 개념적으로 단순한 함수가 하나의 긴 (그러나 단순한) case-statement를 가진다면, 다양한 경우에 많은 작을 것을 수행해야 하는 경우에는 더 긴 function도 괜찮다.
하지만, 만약 복잡한 함수일 경우, function이 무엇을 하는지에 대해 고등학교 1학년 학생이 이해하지 어렵다고 의심되는 경우, 더 면밀히 최대 제한을 준수해야만 한다. 설명적인 name과 함께 helper function을 사용하자 (performance-critical하다고 생각되면 compiler에게 in-line으로 요청할 수 있으며 아마 더 잘 작동할 것이다).
function의 또 다른 척도는 local variable의 수이다. 5-10을 초과해서는 안 되며, 그렇지 않으면 잘못된 것을 수행한다. function을 다시 생각하고 더 작은 부분으로 쪼갠다. 사람의 뇌는 일반적으로 7개의 다른 것을 추적하는 것을 유지할 수 있으며 더 많으면 혼란스러워진다. 본인은 똑똑하다는 것을 알지만, 2 주 후 무엇을 했는지 까먹는다.
소스 파일에서, 빈 칸 한줄로 function을 분리하자. 만약 function이 export된다면 EXPORT macro를 closing function brace line 이후 즉시 이어져야만 한다.
int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);
6.1) Function prototypes
function prototypes에서, data type과 함께 parameter name을 포함시킨다. C language에서 필수적이지 않을지라도, Linux에서는 이것을 선호하며 reader에게 가치있는 정보를 추가하는 단순한 방식이기 때문이다.
function declaration과 함께 extern keyword를 사용하지 않는다. 왜냐하면 이것은 lines을 길게 만들고 꼭 필요하지도 않다.
function prototype을 작성할 때, order of elements regular를 유지한다. 예를 들어, 이 function declaration example을 사용할 때:
__init void * __must_check action(enum magic value, size_t size, u8 count,
char *fmt, ...) __printf(4, 5) __malloc;
function prototype의 element 선호되는 순서는:
- storage class (static __always_inline, __always_inline은 기술적으로는 attribute지만 inline 처럼 취급된다)
- storage class attribute (여기서는, __init -- i.e. section declarartions 뿐 아니라 __cold 같은 것들)
- return type (여기서는, void *)
- return type attributes (여기서는, __must_check)
- function name (여기서는, action)
- function parameters(여기서는, (enum magic value, size_t size, u8 count, char *fmt, ...), parameter name은 항상 포함되어야만 한다)
- function parameter attributes (여기서는, __printf(4, 5))
- function behavior attributes (여기서는, __malloc)
function definition (i.e., 실제 function body)의 경우, compiler은 function parameter 이후 function parameter attributes를 허용하지 않는다. 이러한 경우, storage class attributes 이후 와야한다(e.g. 아래는 __printf(4, 5)의 변경된 위치, 위 declaration 예제와 비교):
static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value,
size_t size, u8 count, char *fmt, ...) __malloc
{
...
}
7) Centralized existing of functions
비록 몇몇 사람들이 비난하지만, goto statement 같은 것들을 무조건 jump instruction 형태로 compilers는 빈번히 사용한다.
goto statement는 function이 다양한 위치와 cleanup이 수행되어야하는 것과 같은 몇몇 공통 작업으로부터 존재할 때 자주 나타난다. 만약 cleanup이 필요하지 않다면 직접 return하면 된다.
goto가 왜 존재하는지 또는 goto가 무엇을 하는지를 언급하는 label name을 선택한다. 만약 goto가 buffer를 free한다면 out_free_buffer:는 훌륭한 name example이다. err1: 그리고 err2: 같은 GW-BASIC names은 사용을 피하며 exit path를 추가하거나 제거한다면 이들을 다시 numbering 해야하므로 어떤 방식으로든 정확성을 검증하기 어렵게 한다.
goto를 사용하는 근거:
- 무조건적인 statement는 쉽게 이해하고 따라가게 한다.
- nesting을 줄인다.
- 변경이 일어날 경우 개별 exit point를 업데이트 하지 않아 발생하는 error를 막는다
- 중복된 code를 최적화해 compiler의 작업을 줄인다;)
int func(int a)
{
int result = 0;
char *buffer;
buffer = kmalloc(SIZE, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
if (condition1) {
while (loop1) {
...
}
result = 1;
goto out_free_buffer;
}
...
out_free_buffer:
kfree(buffer);
return result;
}
bug로 인지되는 일반적인 형태는 다음과 같은 one err bugs이다:
err:
kfree(foo->bar);
kfree(foo);
return ret;
code에서 bug는 몇몇 exit paths에서 NULL이다. 일반적으로 이것의 수정은 두 error labels err_free_bar: 와 err_free_foo:로 분리하는 것이다:
err_free_bar:
kfree(foo->bar);
err_free_foo:
kfree(foo);
return ret;
이상적으로 모든 exit paths를 테스트하기 위해 error를 시물레이션해야만 한다.
8) Commenting
Comments는 훌륭하지만 over-commenting은 또한 위험하다. 절대로 code가 어떻게 동작하는지 comment에 설명하려고 노력하지 않는다: code가 작동하는 것을 명백히 작성하는 것이 훨씬 훌륭하며 작성된 코드를 설명하는 것은 시간 낭비이다.
일반적으로, code가 어떻게가 아닌 무엇을 하는지 설명하는 comment를 원한다. 또한 function body 내부에 comment을 두는 것을 피하려고 노력한다: 만약 function이 너무 복잡해 별도로 comment part를 둔다면, 잠시 chapter 6로 가보자. 작은 comment 또는 명백히 어떤 것에 대한 경고를 두며 과도한 comment는 피하려고 노력한다. 대신, function의 head에 comment를 두고, 무엇을 하는지 그리고 가능하다면 왜 하는지를 설명한다.
kernel API function을 commenting할 경우, kernel-doc format을 사용한다. Document/doc-guide 와 scripts/kernel-doc에 세부사항이 존재한다.
긴 comment의 선호되는 style은 다음과 같다:
/*
* This is the preferred style for multi-line
* comments in the Linux kernel source code.
* Please use it consistently.
*
* Description: A column of asterisks on the left side,
* with beginning and ending almost-blank lines.
*/
이들이 basic type 인지 derived type인지 data의 comment 도 또한 중요하다. line 마다 하나의 data declaration을 사용한다 (multiple data declaration을 위한 commas를 사용하지 않는다). 이것은 각 item을 설명하는 작은 comment를 위한 공간을 남겨둔다.
9) You've made a mess of it
아마 long-time Unix user helper에게 GNU emacs이 자동으로 C source를 format하는 것을 들었을 것이지만 그것을 default로 사용하는 것은 이상적이지 못하다(사실 randome type보다 더 나쁘다 - 무제한의 원숭이가 GNU emac으로 typing 하는 것은 절대 좋은 program이 될 수 없다).
그래서, GNU emac을 제거할 수 있거나 saner value를 사용해 변경할 수 있다. 후자를 위해 .emacs file에 다음을 추가할 수 있다.
(defun c-lineup-arglist-tabs-only (ignored)
"Line up argument lists by tabs, not spaces"
(let* ((anchor (c-langelem-pos c-syntactic-element))
(column (c-langelem-2nd-pos c-syntactic-element))
(offset (- (1+ column) anchor))
(steps (floor offset c-basic-offset)))
(* (max steps 1)
c-basic-offset)))
(dir-locals-set-class-variables
'linux-kernel
'((c-mode . (
(c-basic-offset . 8)
(c-label-minimum-indentation . 0)
(c-offsets-alist . (
(arglist-close . c-lineup-arglist-tabs-only)
(arglist-cont-nonempty .
(c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only))
(arglist-intro . +)
(brace-list-intro . +)
(c . c-lineup-C-comments)
(case-label . 0)
(comment-intro . c-lineup-comment)
(cpp-define-intro . +)
(cpp-macro . -1000)
(cpp-macro-cont . +)
(defun-block-intro . +)
(else-clause . 0)
(func-decl-cont . +)
(inclass . +)
(inher-cont . c-lineup-multi-inher)
(knr-argdecl-intro . 0)
(label . -1000)
(statement . 0)
(statement-block-intro . +)
(statement-case-intro . +)
(statement-cont . +)
(substatement . +)
))
(indent-tabs-mode . t)
(show-trailing-whitespace . t)
))))
(dir-locals-set-directory-class
(expand-file-name "~/src/linux-trees")
'linux-kernel)
이것은 emacs을 ~/src/linux-trees 아래에서 C file을 위한 kernel coding style으로 할 수 있도록 한다.
하지만 만약 emac을 위 fomatting 하는 데 실패하더라도 모든 것을 잃지는 않는다: indent를 사용한다.
이제 다시, GNU indent는 GNU emac이 가지고 있는 것과 같은 brain-dead setting(정신나간 설정)를 가지며, 이것은 왜 몇몇 command line option을 주어질 필요가 있는지를 나타낸다. 하지만, 나쁘진 않은데, 왜냐하면 GNU indent makers는 K&R의 권위를 인지하기 때문이다(GNU 사람들이 나쁜 건 아니고, 이 문제에 대해 잘못 가이드 받은 것이다). 그래서 -kr -i8 옵션으로 indent를 준다. (K&R, 8 character indents), 또는 scripts/Lindent를 사용하며, 최근 style에서 indent한다.
indent는 많은 옵션을 가지고 있으며 특히 comment를 re-formatting 하는 것에 관해서는 man page를 확인하자. 하지만 기억하자: indent는 나쁜 프로그래밍을 고치지는 못한다.
clang-format tool을 또한 사용해 이러한 rule를 도울 수 있고 빠르게 자동으로 code를 re-format할 수 있으며, coding style mistakes, typos and possible improvement를 찾기 위해 전체 파일을 리뷰한다. #includes 정렬, variables/macros 정렬, 텍스트 포맷 재정렬 과 다른 유사한 작업에 유용하다. Documentation/dev-tools/clang-format.rst 보고 더 자세히 알아보자.
만약 EditorConfig 같은 editor를 사용한다면 line endings와 indentation 같은 몇몇 기본 editor settings는 자동으로 설정될 것이다. 더 많은 정보는 공식 EditorConfig Website를 참고한다:https://editorconfig.org/
EditorConfig
What is EditorConfig? EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. The EditorConfig project consists of a file format for defining coding styles and a collection o
editorconfig.org
10) Kconfig configuration files
source tree 전체의 모든 Kconfig* configuration file에 관해, indentation은 다소 다르다. config definition 아래의 lines는 one tab으로 indent되지만, help text는 two spaces를 추가해 indent한다. 예를 들어:
config AUDIT
bool "Auditing support"
depends on NET
help
Enable auditing infrastructure that can be used with another
kernel subsystem, such as SELinux (which requires this for
logging of avc messages output). Does not do system-call
auditing without CONFIG_AUDITSYSCALL.
심각하게 위험한 features (특정 filesystemd을 위한 지원을 작성하는 것 같은)은 prompt string에서 이를 명확히 조언해야만 한다.
config ADFS_FS_RW
bool "ADFS write support (DANGEROUS)"
depends on ADFS_FS
...
configuration files에서 전체 documentation은 Kconfig Language 파일을 확인하자.
11) Data structures
Data structure가 생성되고 파괴되는 single-threaded 환경 외부에서 볼 수 있는 Data structures는 reference count를 항상 가져야만 한다. kernel에서, garbage collection은 존재하지 않으며 (그리고 kernel garbage collection은 느리고 비효율적), 이것은 모든 사용하는 reference count를 절대적으로 가져야함을 의미한다.
Reference counting은 locking을 회피할 수 있고 다수의 users가 병렬로 data structure를 접근하도록 하며 - 단지 잠들거나 잠시동안 다른 일을 했다는 이유만으로 갑자기 사라지는 것에 관한 걱정을 할 필요가 없다.
locking은 reference counting을 대체하는 것이 아니다. Locking은 data structure를 일관적으로 유지하는데 사용되는 반면, reference counting은 memory management technique이다. 주로 둘다 필요하며 각각을 혼돈하지 않아야 한다.
많은 data structures는 다른 class의 user가 있을 경우 2 level reference counting을 가질 수 있다. subclass count는 subclass users의 수를 count하며, subclass count가 0일 경우에만 global count 감소시킨다.
multi-level-reference-counting 의 예제는 memory management (struct mm_struct: mm_users and mm_count)와 filesystem code (struct super_block: s_count and s_active)에서 발견할 수 있다.
Remember: 만약 다른 thread가 data structure를 발견할 수 있고 reference count가 없다면 거의 확실히 bug를 가진다.
12) Macros, Enums and RTL
enum에서 constants와 labels의 Name은 대문자이다.
#define CONSTANT 0x12345
Enums는 몇몇 연관된 constant를 정의할 때 선호된다.
CAPITALIZED macro names는 적절하지만 function 같은 macro는 소문자로 명명될 수도 있다.
일반적으로, inline functions이 function 같은 macro보다 선호된다.
여러 statements를 가진 macro는 do -while block으로 감싼다:
#define macrofun(a, b, c) \
do { \
if (a == 5) \
do_this(b, c); \
} while(0)
parameter를 사용하지 않는 function-like macros는 사용되지 않는 variable 이슈를 피하기 위해 static inline function로 대체되어야만 한다.
static inline void void fun(struct foo *foo)
{
}
hitorical practices 때문에, 많은 file은 parameters를 평가하기 위해, "cast to (void)" 접근을 수행한다. 하지만, 이러한 방식은 권장되지 않는다. inline functions은 "expression with side effects evaluated more than once" 이슈를 처리하며, unused-variable 문제를 피하며 일반적으로 몇몇 이유로 macro보다 더 잘 문서화된다.
/*
* Avoid doing this whenever possible and instead opt for static
* inline function
*/
#define macrofun(foo) do { (void) (foo); } while (0)
macro를 사용할 때 피하기 위한 것:
- control flow에 영향을 끼치는 macros
매우 안좋은 생각이다. function call처럼 보이지만 function을 호출하는 것이 존재한다; code를 읽을 사람의 내부 parser를 깨뜨리지 말자.#define FOO(x) \ do { \ if (blah(x) < 0) \ return -EBUGGERED; \ } while (0) \ - magic name을 가진 local variable에 의존하는 macro
좋은 것처럼 보이지만, 누군가 code를 읽을 때 겉보기에는 단순한 변경으로부터 깨지기 쉽기 때문에 혼란스러울 수 있다.#define FOO(val) bar(index, val) - l-value로 사용된 arguments를 가진 macros: FOO(x) = y; 누군가 FOO를 inline function으로 변경한다면 물릴 것이다.
- precedence에 관한 까먹음: expressions을 사아ㅛㅇ해 constants를 정의하는 macro는 parentheses로 감싸져야만 한다. parameter 사용하는 macro에서도 유사한 이슈를 인지하자.
#define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3) - function 같은 macro에서 local variables를 정의할 때 namespace 충돌:
ret은 local variable에서 흔한 이름이다- __foo_ret은 거의 사용되지 않으므로 기본 variable과 충돌이 거의 없다.#define FOO(x) \ ({ \ typeof(x) ret; \ ret = calc_ret(x); \ (ret); \ })
cpp(전처리, C PreProcessor) manual은 macros를 철저히 다룬다. gcc internals manual 또한 kernel에서 assembly language와 함께 빈번히 상ㅇ되는 RTL를 커버한다.
13) Printing kernel messages
kernel 개발자는 문해력있는 사람으로 보이기를 좋아한다. 좋은 인상으로 만들기 위해 kernel message의 spelling에 유념하자. dont같은 잘못된 contractions을 사용하지 말자; do not 또는 don't를 대신 사용한다. messages를 정확하고, 명확하고 분명하게 작성한다.
Kernel message는 마침표로 종료되지 말아야 한다.
parentheses (%d)에 숫자는 아무론 가치를 추가하지 않으므로 피해야만 한다.
<linux/dev_printk.h>에는 많은 driver model 진단 macro가 있으며, 이들은 message와 올바른 device와 driver를 매칭하며 올바른 level로 tag된다: dev_err(), dev_warn(), dev_info(), 등등. 특정 device와 연관되지 않은 messages의 경우, <linux/printk>는 pr_notice(), pr_info(), pr_warn(), pr_err() 등등을 정의한다. driver가 적절히 잘 장동할 경우, 잘못되지 않으면 dev_dbg/pr_debug 사용을 선호하자.
좋은 debugging message를 생각하는 것은 꽤 어려울 수 있다; 일단 메시지를 가지면 문제를 원격으로 해결하는데 큰 도움이 될 수 있다. 하지만, debug message printing은 다른 non-debug message를 printing하는 것보다 다르게 다뤄진다. 다른 pr_XXX() functions은 조건없이 print되는 반면, pr_debug()는 그렇지 않다; 각 DEBUG가 정의되지 않거나 CONFIG_DYNAMIC_DEBUG가 설정되지 않으면 default로 컴파일되지 않는다. dev_dbg() 또한 그러하며 이미 DEBUG가 enable 된 것에 VERBOSE_DEBUG를 사용해 dev_vdeb() message를 추가할 수 있다.
많은 subsystems는 일치하는 Makefile에서 -DDEBUG를 켜기 위해 Kconfig debug options을 가진다; #define DEBUG를 가진 특정 파일도 있다. 그리고 debug message가 무조건적으로 출력될 때, #ifdef section 내부에 이미 있을 경우 printk(KERN_DEBUG ...)가 사용될 수 있다.
14) Allocating memory
kernel은 다음 general purpose memory allocators: kmalloc(), kzalloc(), kmalloc_array(), kcallc(), vmalloc(), and zalloc()을 제공한다. 이들에 관한 추가 정보는 API document를 참조하자. Documentation/core-api/memory-allocation.rst
struct의 크기를 전달하기 위한 선호되는 형식은 다음과 같다:
p = kmalloc(sizeof(*p), ...);
struct name을 작성하는 다른 형식은 가독성을 해치고 pointer variable type이 변경되었지만 memory allocator로 전달된 동일한 sizeof가 변경되지 않을 경우 버그를 유발한다.
void pointer로 return 된 value를 cating 하는 것은 불필요하다. void pointer를 다른 특정 pointer type을 convertion하는 것은 C programming language에서는 보장된다.
arraay를 할당하기 위해 선호되는 형식은 다음과 같다:
p = kmalloc_array(n, sizeof(...), ...);
zerod array를 할당하기 위해 선호되는 형식은 다음과 같다.
p = kcalloc(n, sizeof(...), ...);
두 형식 모두 n * sizeof(...) 할당에서 overflow를 체크하며 만약 발생했다면 NULL를 return 한다.
일반적인 allocation functions 모두는 __GFP_NOWARN 없이 사용될 경우 실패 시 stack dump를 방출하므로 NULL이 반환될 때 추가 실패 메시지를 방출하는 것은 아무 소용이 없다.
15) The inline disease
gcc가 inline으로 불리는 옵션으로 더 빠르게 속도를 올리는 마법을 가지고 있다는 오해가 있는 것처럼 보인다. inline의 사용은 적절할 수 있지만 (예를 들어 macro를 대체하는 용도로, chapter 12), 그렇지 않은 경우가 많다. inline keyword의 남용은 kernel이 훨씬 커지고, CPU의 더 큰 icache footprint와 단순하게 pagecache를 위한 memory 사용 공간이 적어지기 때문에 전체 system은 느려진다. 생각해보자; pagecache miss는 disk seek를 유발하며 5 milliseconds정도 걸린다. 이 5 milliseconds 동안 많은 cpu cycle이 소요된다.
합리적인 rule은 code가 3줄 이상이면 inline function으로 두지 않는 것이다. 이 rule의 예외는 parameter가 compile time constatnt로 알려진 경우이며, 이러한 constantness 결과로 compiler는 compile time에 대부분의 function을 최적화 할 수 있다. 후자의 좋은 예시는 kmalloc inline function 이다.
주로 사람들은 static 이면서 항상 한번만 사용되는 것에 inline 을 추가하는 것은 항상 옳다고 주장하는데, 이는 tradeoff가 없기 때문이다. 기술적으로는 올바르지만, gcc는 도움없이 자동으로 inlining을 할 수 있으며 두 번째 사용자가 나타나면 inline을 제거하는 유지보수 이슈는 gcc가 어차피 했을 일을 하라는 hint의 잠재적 가치보다 크다.
16) Function return values and names
Functions은 많은 종류의 value를 return 할 수 있으며 가장 흔한 경우는 function이 성공했는지 실패했는지를 가리키는 value이다. 이러한 value는 error-code integer로 표현될 수 있으며 (-Exxx = failure, 0 = success) 또는 succeeded boolean (0 == failure, non-zero = success).
위 두 종류의 표현을 혼합해서 사용하는 것은 찾기 어려운 버그의 풍부한 원천이다. C language가 integers와 booleans 간 강력한 구분을 포함한다면 compiler는 이러한 실수를 찾을 것 이지만, 그렇지 못하다. 이러한 bug를 막기 위해 항상 다음 convention을 따른다.
If the name of a function is an action or an imperative command,
the function should return an error-code integer. If the name
is a predicate, the function should return a "succeeded" boolean.
예를 들어, add work는 command이며 add_work() function은 성공 시 0을 반환하거나 실패시 -EBUSY를 반환한다. 같은 방식으로 PCI device present는 predicate이며 pci_dev_present() function은 일치하는 device를 찾을 경우 성공하며 1를 반환하며 그렇지 않으면 0을 반환한다.
모든 EXPORTed functions는 이러한 convetion을 반드시 존중해야만 하며, 모든 public function도 그래야만 한다. Private(static) function는 필수는 아니지만 추천된다.
return value가 연산의 성공 여부가 아닌 실제 연산의 결과를 반환하는 Function은 이 rule의 적용을 받지 않는다. 일반적으로 out-of-range value를 반환하여 실패를 나타낸다. 전형적인 예시는 pointer를 return하는 function이다; NULL 또는 ERR_PTR mechanism으로 failure를 보고한다.
17) Using bool
Linux kernel bool type은 C99 _Bool type을 위한 alias이다. bool value는 0 or 1로만 평가될 수 있으며 암시적으로 또는 명시적으로 bool로 변환하면 true or false로 자동으로 값을 변경된다. bool type을 사용할 때 bugs 유형을 제거하는 !! construction은 필요하지 않는다.
bool values와 함께 동작할 경우 true 와 false definitions는 1 과 0 대신 사용되어야만 한다.
bool function return type과 stack variables는 항상 적절할 때마다 사용하면 괜찮다. bool의 사용은 가독성을 향상을 위해 권장되며 주로 boolean value를 저장하는 int보다 더 나은 옵션이다.
만약 cache line layout 또는 value matter의 크기가 중요하다면, bool 은 사용하지 말자. size와 alignment는 compile된 architecture에 따라 달라지기 때문이다. alignment와 size로 최적화된 structure는 bool을 사용하지 않아야한다.
만약 structure가 true/false value를 가진다면 1 bit member로 이들을 bitfield로 통합하거나 u8 같은 적절한 fixed witdth type 사용을 고려하자.
그렇지 않은 경우 structures와 argument에서 제한된 bool의 사용은 가독성을 향상시킬 수 있다.
18) Don't re-invent the kernel macros
header file include/linux/kernel.h은 명시적으로 일부 매크로를 코딩하는 것보다 는 사용해야만 하는 macros를 포함되어 있다. 예를 들어 array의 length를 계산할 필요가 있다면, macro를 활용하자:
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
비슷하게도, 몇몇 structure member의 크기를 계산할 필요가 있다면 다음을 사용하자:
#define sizeof_field(t, f) (sizeof(((t*)0)->f))
min()과 max() macros는 필요하다면 업격한 type checking이 있다. 해당 header file을 자유롭게 검토해 코드에서 재정의하지 않아야하는 이미 정의된 것들을 확인하자.
19) Editor modelines and other cruft
몇몇 editors는 special markers를 가리키는 source file에 포함된 configuration infromation을 interpret 할 수 있어야한다. 예를 들어, emacs는 다음과 같은 mark된 lines을 interpret한다.
-*- mode: c -*-
또는 다음과 같은:
/*
Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/
Vim은 다음과 같은 markers를 interpret 한다:
/* vim:set sw=8 noet */
source file에서 이러한 것들을 포함하지 말자. 사람들은 개별 editor configuration을 가지며 source files는 이들을 override하지 않는다. 이것은 indentation과 mode configuration을 위한 markers를 포함한다. 사람들은 custom mode를 사용할 수도 적절히 동작하도록 indentation을 생성하는 다른 magic method를 가질 수도 있다.
20) Inline assembly
architecture-specific code에서, inline assembly는 CPU 또는 platform functionality와 함께 interface하는데 사용할 필요가 있을 수 있다.필요할 때 이것을 주저하지 말자. 하지만 C가 해당 일을 할 수 있을 경우 inline assembly를 사용하지 말자. 가능할 경우 C에서 hardware를 제어할 수 있고 해야만한다.
반복적으로 비슷한 inline assembly를 작성하는 것 보다 inline assembly를 감싸는 단순한 helper function을 작성을 고려하자. inline assembly는 C parameter를 사용할 수 있다.
크고 복잡한 assembly functions은 .S file에 두어야만 하고 C header file에 C prototype을 정의하자. assembly functions을 위한 C prototype은 asmlinkage를 사용해야만 한다.
volatile로 asm statement를 mark 할 필요가 있는데, GCC가 어떠한 side effect를 알리지 않는다면 제거하는 것을 막기 위해서이다. 항상 붙일 필요가 있는 것은 아니며 불필요하게 사용하면 최적화를 제한할 수도 있다.
multiple instructions을 포함하는 single inline assembly statement를 작성할 때, 분리된 quoted string에서 분리된 line으로 각 instruction을 두고 assembly output에서 다음 instruction을 적절히 indent하기 위해 \n\t와 함께 마지막을 제외하고 각 string을 끝낸다.
asm ("magic %reg1, #42\n\t"
"more_magic %reg2, %reg3"
: /* outputs */ : /* inputs */ : /* clobbers */);
21) Conditional Compilation
가능할 때마다, .c file에서는 preprocessor conditionals (#if, #ifdef)를 사용하지 않는다; code를 읽기 어렵게 하고 logic을 따라가기 어렵게 한다. 대신, .c file에 사용을 위해 function을 정의하는 header file에서 이러한 조건을 사용하고 #else의 경우에서 no-op stub version을 제공하며 .c file으로부터 조건 없는 이러한 function을 호출하자. compiler는 stub calls을 위해 어떤 code 생성을 피하고 동일한 결과를 생성하지만 logic은 여전히 따라가기 쉽게 남겨져 있다.
function의 부분 또는 expression의 부분보다 전체 function을 compile하는 것을 선호하자. expression에서 ifdef를 두는 것보다 분리된 helper function으로 모든 또는 부분 expression을 factor out 하고 function에 조건을 적용하자.
만약 잠재적으로 특정 configuration에서 사용되지 않은 variable 또는 function을 가지고 있다면 그리고 compiler가 unused 상태인 definition에 관해 경고한다면, preprocessor conditional에서 감싸기 보단 __maybe_unused 같은 definition을 mark 하자. (하지만, 만약 function 또는 vraible이 항상 사용되지 않으면 삭제한다.)
code 내부에서, 가능하다면, IS_ENABLED macro를 사용해 Kconfig symbol을 C boolean expression으로 변경하고, 일반적인 C 조건문에 사용한다:
if (IS_ENABLED(CONFIG_SOMETHING)) {
...
}
compiler는 conditional away를 constant-fold할 것이며, #ifdef와 함께 사용되는 것처럼 code의 block을 포함하거나 제외하여, 결국 어떠한 runtime overhead를 추가하지 않을 것이다. 하지만, 이러한 접근은 C compiler가 block 내부 코드를 볼 수 있게 하고 correctness를 체크할 수 있게 한다(syntax, types, symbol references, etc). 그러므로, 만약 조건이 만족하지 않을 때 존재하지 않는 symbols을 참조하는 code라면 여전히 #ifdef를 사용해야한다.
non-trivial(사소하지 않은) #if 또는 #ifdef block의 끝에서(여러줄일 경우), #endif 이후 같은 line에서 comment를 두어, conditional expression이 사용됨을 알린다. 예를 들어:
#ifdef CONFIG_SOMETHING
...
#endif /* CONFIG_SOMETHING */
22) Do not crash the kernel
일반적으로 kernel을 crash하는 결정은 kernel 개발자보다는 user에게 달려있다.
Avoid panic()
panic()는 system boot 동안에만 주로 그리고 유심히 사용되어야만 한다. 예를 들어, panic()은 boot 동안 out of memory로 동작할 때 그리고 지속할 수 없을 때 받아들여진다.
Use WARN() rather than BUG()
BUG(), BUG_ON() 또는 VM_BUG_ON() 같은 BUG() variant를 사용하는 새로운 code를 추가하지 않는다. 대신, WARN*(), WARN_ON_ONCE()를 더 선호하며, variant를 사용하고, 가능하다면 recovery code와 함께 사용한다. Recovery code는 부분적으로 revocer 될 합리적인 방법이 없다면 필수는 아니다.
"나는 error handling 하기 귀찮다"는 이유로 BUG()을 사용하지 말자. 지속될 수 없는 방식으로 major internal corruption은 여전히 BUG()를 사용하지만 좋은 판단은 아니다.
Use WARN_ON_ONCE() rather than WARN() or WARN_ON()
WARN_ON_ONCE()는 일반적으로 WARN() 또는 WARN_ON()보다 선호되며 여러번 발생한다면 주어진 warning condition을 위해 일반적이기 때문이다. kernel log를 wrap하고 채울 수 있으며 추가 문제로 과도한 loging은 system을 충분히 느리게할 수도 있다.
Do not WARN lightly
WARN*()은 예상치 못하고 절대 발생해선 안되는 경우 의도된다. WARN*() macros는 일반 동작 중 일어날 것 같은 어떤 것을 위해 사용되서는 안된다. pre- 또는 post-condition assert가 없다. 다시: WARN*()는 절대 쉽게 발생할 것 같은 조건을 위해 사용되서는 안되며, 예를 들어, user space action 같은 상황은 사용해선 안된다. pr_warn_once()는 가능한 대안이며 만약 user에게 문제를 알릴 필요가 있을 경우 가능하다.
Do not worry about panic_on_warn_users
panic_on_warn에 관한 몇가지 첨언: panic_on_warn은 kernel option으로 사용가능하며 많은 user는 이 option을 설정한다. 이것은 위에 왜 WARN을 가볍게 사용하지 말라는 이유다. 하지만, panic_on_warn users의 존재는 WARN*() 사용을 피하기 위한 명백한 이유가 아니다. 즉, panic_on_warn을 활성화하는 user가 WARN*()이 발생할 경우 커널을 crash하도록 명시적으로 요청했기 때문이며, 이러한 user는 crash할 가능성이 어느 정도 높은 시스템의 결과에 대처할 준비가 되어 있어야 한다.
Use BUILD_BUG_ON() for compile-time assertions
BUILD_BUG_ON()의 사용은 가능하며 권장된다. 왜냐하면 compile-time assertion은 runtime에 아무런 영향이 없기 때문이다.
'ARM64 Linux Kernel' 카테고리의 다른 글
| Yocto custom linux 환경 설정 (1) | 2025.04.26 |
|---|---|
| Linux Device Tree (0) | 2024.10.04 |