Linux kernel v4.4에서 간단한 블록 장치 드라이버 만들어보기

per-cpu 변수 동적으로 만들기

alloc_percpu을 이용해서 per-cpu변수를 동적으로 만들 수 있습니다. alloc_percpu함수 먼저 struct percpu_data 구조체의 객체를 만듭니다. struct percpu_data에는 void *ptrs[NR_CPUS] 배열이 있는데 프로세서 갯수만큼 요청받은 크기의 메모리를 할당합니다. percpu_data 객체는 시스템에 전역적인 객체이고, 이 객체가 각 프로세서별 데이터를 관리하는 것입니다. 그리고 percpu_data 객체를 반환합니다.

void *__alloc_percpu(size_t size, size_t align)
{
    int i;
	struct percpu_data *pdata = kmalloc(sizeof (*pdata), GFP_KERNEL);

	if (!pdata)
		return NULL;

	for (i = 0; i < NR_CPUS; i++) {
		if (!cpu_possible(i))
			continue;
		pdata->ptrs[i] = kmem_cache_alloc_node(
				kmem_find_general_cachep(size, GFP_KERNEL),
				cpu_to_node(i));

		if (!pdata->ptrs[i])
			goto unwind_oom;
		memset(pdata->ptrs[i], 0, size);
	}

	/* Catch derefs w/o wrappers */
	return (void *) (~(unsigned long) pdata);

그런데 마지막 반환값을 보면 pdata변수를 그대로 반환하는게 아니라 ~ 연산자를 써서 비트를 반전시켜서 반환합니다. 왜냐면 주석에도 설명했듯이 per-cpu 변수를 일반 변수 쓰듯이 접근하면 안되기때문에 비트를 반전시켜서 접근이 안되도록 한 것입니다.

per-cpu변수를 해지하는 함수는 free_percpu입니다. 해지할 객체의 ptrs 배열에 있는 각 프로세서별 메모리를 해지하고, 마지막으로 객체 자체를 해지합니다. free_percpu 코드의 시작 부분에는 전달된 per-cpu변수의 비트를 반전시킵니다. 그래야 원래의 per-cpu 변수에 접근할 수 있기 때문입니다.

alloc_percpu함수로 할당받은 per-cpu변수에서 프로세서별 변수에 접근하기 위해서per_cpu_ptr 매크로함수를 사용합니다. 

/* 
 * Use this to get to a cpu's version of the per-cpu object allocated using
 * alloc_percpu.  Non-atomic access to the current CPU's version should
 * probably be combined with get_cpu()/put_cpu().
 */ 
#define per_cpu_ptr(ptr, cpu)                   \
({                                              \
        struct percpu_data *__p = (struct percpu_data *)~(unsigned long)(ptr); \
        (__typeof__(ptr))__p->ptrs[(cpu)];    \
})

alloc_percpu함수로 받은 값의 비트를 반전시키면 percpu_data 객체를 얻을 수 있습니다. 그리고 percpu_data 객체의 ptrs[cpu] 값이 해당 프로세서용 변수의 포인터입니다.

이제 실제로 어떻게 사용하는지 예를 한번 보겠습니다. 다음은 include/linux/genhd.h 파일에 정의된 init_disk_stats 함수입니다.

static inline int init_disk_stats(struct gendisk *disk)
{
    disk->dkstats = alloc_percpu(struct disk_stats);
	if (!disk->dkstats)
		return 0;
	return 1;
}
static inline void free_disk_stats(struct gendisk *disk)
{
    free_percpu(disk->dkstats);
}

gendisk의 dkstats 필드에 disk_stats 타입의 per-cpu 변수를 생성합니다. 그리고 free_disk_stats함수에서 disk->dkstats 변수를 해지합니다. 다음은 per_cpu_ptr 함수의 예제입니다.

#define __disk_stat_add(gendiskp, field, addnd)     \
	(per_cpu_ptr(gendiskp->dkstats, smp_processor_id())->field += addnd)

참고로 smp_processor_id()는 이 코드가 실행된 프로세서의 번호를 반환해줍니다. 그리고 per_cpu_ptr함수의 결과값은 lvalue입니다. 따라서 함수의 결과값에 곧바로 ->같은 연산자를 적용할 수 있습니다.

댓글

댓글 본문
작성자
비밀번호
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기