리눅스 커널과 드라이버에서 __test_and_set_bit()와 __test_and_clear_bit() 함수를 많이 씁니다.
두 함수 중 test_and_set_bit()를 써서 비트를 처리하는 코드를 보겠습니다.
다음은 워크를 워크큐에 큐잉하는 queue_work_on() 함수입니다.
[kernel/workqueue.c]
1 bool queue_work_on(int cpu, struct workqueue_struct *wq,
2 struct work_struct *work)
3 {
4 bool ret = false;
5 unsigned long flags;
6
7 local_irq_save(flags);
8
9 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }
13
14 local_irq_restore(flags);
15 return ret;
16}
9번째 줄 코드를 보겠습니다.
9 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }
work_data_bits(work) 매크로 함수는 struct work_struct 구조체 주소에서 struct work_struct.data 멤버를 읽습니다. 이 값이 WORK_STRUCT_PENDING_BIT(1)이면 9~12줄 코드를 실행하지 않고 if 문을 빠져나옵니다.
test_and_set_bit() 함수는 리눅스 커널 자료구조 함수 중 하나입니다.
test_and_set_bit(A, B); 와 같이 호출하면 A와 B란 변수 비트를 AND 연산한 다음 결과가 1이면 1을 리턴하고 반대로 0이면 0을 리턴합니다. 연산 결과에 상관없이 B이란 변수에 A비트를 설정합니다.
이해를 돕기 위해 9~12줄 코드를 다른 코드로 쉽게 표현하면 다음과 같습니다.
1 if (work->data == WORK_STRUCT_PENDING_BIT) {
2
3 } else
4 work->data =| WORK_STRUCT_PENDING_BIT;
5 __queue_work(cpu, wq, work);
6 ret = true;
7 }
work->data가 WORK_STRUCT_PENDING_BIT이면 if 문을 만족하는데 2번 줄 코드와 같이 동작도 안 하고 if 문을 빠져나옵니다. 2번째 줄 코드와 같이 실행할 코드가 없기 때문입니다. 대신 work->data가 WORK_STRUCT_PENDING_BIT 가 아니면 else문을 실행합니다.
if 문 조건을 만족하면 아무 동작을 안 하는 코드는 모양이 이상하니 if 문에 ! 조건으로 바꿔 코드 순서를 바꿔 보면 다음과 같습니다.
if ( !(work->data == WORK_STRUCT_PENDING_BIT)) {
work->data =| WORK_STRUCT_PENDING_BIT;
__queue_work(cpu, wq, work);
ret = true;
}
work->data 멤버가 WORK_STRUCT_PENDING_BIT 매크로가 아니면 work->data에 WORK_STRUCT_PENDING_BIT를 저장하고 __queue_work() 함수를 호출하는 겁니다.
test_and_set_bit() 함수를 다른 코드로 바꿔서 설명을 드렸습니다. test_and_set_bit() 함수는 위에 바꾼 코드와 같이 struct work_struct.data 멤버가 WORK_STRUCT_PENDING_BIT 매크로가 아니면 struct work_struct.data 멤버를 WORK_STRUCT_PENDING_BIT 매크로로 설정하고 0을 리턴합니다.
반대로 struct work_struct.data 멤버가 WORK_STRUCT_PENDING_BIT 매크로이면 struct work_struct.data 멤버를 WORK_STRUCT_PENDING_BIT 매크로로 설정한 후 1을 리턴합니다.
여기까지 test_and_set_bit() 함수를 어느 커널 코드에서 쓰는지 알아봤으니 이번에 이 함수가 어떻게 동작히는지 확인합시다.
해당 코드 구현부는 다음 코드에서 확인할 수 있습니다.
static inline int __test_and_set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long old = *p;
*p = old | mask;
return (old & mask) != 0;
}
static inline int __test_and_clear_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr);
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
unsigned long old = *p;
*p = old & ~mask;
return (old & mask) != 0;
}
__test_and_set_bit() 함수와 __test_and_clear_bit() 함수 구현부를 그대로 가져와 리눅스 시스템 프로그래밍으로 동작을 확인합니다. 먼저 소스 코드를 소개합니다.
240 #include <stdio.h>
241 enum {
242 CHAINIV_STATE_INUSE = 1
243 CHAINIV_STATE_DISUSE = 2,
244 CHAINIV_STATE_ONLINE = 3,
245 CHAINIV_STATE_OFFLINE = 4
246 };
247
248
249 int __test_and_clear_bit(int nr, volatile unsigned long *addr)
250 {
251 unsigned long mask = (1UL << ((nr) % 32));
252 unsigned long *p = ((unsigned long *)addr) + ((nr) / 32);
253 unsigned long old = *p;
254
255 *p = old & ~mask;
256 return (old & mask) != 0;
257 }
258
259 int __test_and_set_bit(int nr, volatile unsigned long *addr)
260 {
261 unsigned long mask = (1UL << ((nr) % 32));
262 unsigned long *p = ((unsigned long *)addr) + ((nr) / 32);
263 unsigned long old = *p;
264
265 *p = old | mask;
266 return (old & mask) != 0;
267 }
268
269 int main()
270 {
271 unsigned long state = 0;
272
273 printf(" state: %ld, line[%d] \n", state, __LINE__);
274
275 if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
276 printf(" state: %ld, line[%d] \n", state, __LINE__);
277 } else {
278 printf(" state: %ld, line[%d] \n", state, __LINE__);
279 }
280
281 if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
282 printf(" state: %ld, line[%d] \n", state, __LINE__);
283 } else {
284 printf(" state: %ld, line[%d] \n", state, __LINE__);
285 }
286
287 if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
288 printf(" state: %ld, line[%d] \n", state, __LINE__);
289 } else {
290 printf(" state: %ld, line[%d] \n", state, __LINE__);
291 }
292
293 if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
294 printf(" state: %ld, line[%d] \n", state, __LINE__);
295 } else {
296 printf(" state: %ld, line[%d] \n", state, __LINE__);
297 }
298
299 return 1;
300 }
리눅스 시스템 프로그램으로 작성하면 되는데 위 코드를 작성 후
main.c 파일로 저장한 후 다음 명령어로 컴파일을 합시다.
gcc -o test_main main.c
컴파일 후 test_main 어플리케이션을 실행하면 다음과 같은 결과를 볼 수 있습니다.
state: 0, line[273]
state: 0, line[278]
state: 1, line[284]
state: 1, line[288]
state: 0, line[294]
위 로그가 어떻게 출력됐는지 해당 코드를 자세히 살펴봅시다.
첫 로그 실행 코드를 봅시다.
271 unsigned long state = 0;
272
273 printf(" state: %ld, line[%d] \n", state, __LINE__);
state이란 변수가 0이니 0을 출력합니다.
이번에는 두 번째 로그입니다.
275 if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
276 printf(" state: %ld, line[%d] \n", state, __LINE__);
277 } else {
278 printf(" state: %ld, line[%d] \n", state, __LINE__);
279 }
CHAINIV_STATE_INUSE 값이 1이므로 state가 0이니 __test_and_clear_bit() 함수는 0을 반환합니다.
동시에 state 값인 0 에서 1을 Clear합니다. 결과는 0입니다.
따라서 다음과 같이 278줄 코드를 실행하는 로그를 출력합니다.
state: 0, line[278]
다음 세 번째 로그 실행 코드를 분석합시다.
281 if(__test_and_set_bit(CHAINIV_STATE_INUSE, &state)) {
282 printf(" state: %ld, line[%d] \n", state, __LINE__);
283 } else {
284 printf(" state: %ld, line[%d] \n", state, __LINE__);
285 }
state 값이 0이니 __test_and_set_bit() 함수는 0을 반환합니다.
따라서 else문인 284 코드를 실행합니다. 동시에 state를 1로 설정합니다.
해당 로그는 다음과 같습니다.
state: 1, line[284]
이번에 마지막 로그를 분석합니다.
293 if(__test_and_clear_bit(CHAINIV_STATE_INUSE, &state)) {
294 printf(" state: %ld, line[%d] \n", state, __LINE__);
295 } else {
296 printf(" state: %ld, line[%d] \n", state, __LINE__);
297 }
state가 1이니 __test_and_clear_bit() 함수는 1을 반환하며 1을 Clear합니다.
따라서 294줄 코드를 실행합니다.
결과 로그는 다음과 같습니다.
state: 0, line[294]
리눅스 커널에서 보이는 코드를 보면 너무 어렵게 생각말고 이렇게 코드를 분리해서 테스트 해 봅시다.
바로 어떻게 동작하는지 확인할 수 있습니다.
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
[리눅스커널][디버깅] 유저공간 High Memory Zone 페이지 할당 과정 (0) | 2023.05.06 |
---|---|
[Linux][Kernel] preempt_disable()/preempt_enable() 주의 사항 (0) | 2023.05.06 |
[리눅스] printk 아규먼트 포멧 (0) | 2023.05.06 |
[C언어] 포인터 (p + 1) 연산 (0) | 2023.05.06 |
[Linux][GCC]## 매크로 - 심볼 생성 (0) | 2023.05.06 |