리눅스 커널을 보던 중 Container_of 라는 매크로라는 녀석을 자주 보게 된다.

container_of - cast a member of a structure out to the containing structure

  
SYNOPSIS
container_of  (ptr, type, member);

ARGUMENTS

ptr
the pointer to the member.

type
the type of the container struct this is embedded in.

member
the name of the member within the struct.

하는 역활은 한 구조체의 멤버만 알고 있을 경우 그 구조체의 시작 주소를 구할 때 사용한다. 예를 들어 다음과 같은 경우에 사용된다.

struct tagTEMP
{
   int X;
   int Y;

  struct list *next;
};

void calc(struct list *node )
{
   struct tagTEMP *pTEMP;

  pTEMP = container_of(next, struct tagTEMP, next );
}

동기화가 뭔지에 대해선 설명하지 않겠다. 리눅스에서 쓰레드 동기화를 위해 사용하는 mutex 와 semaphore 에 대해서 간략한 사용방법을 설명한다.

1. 뮤텍스(mutex)

리눅스에서 사용하는 mutex는 pthread에서 사용하는 mutex를 사용한다. 이 pthread mutex는 프로세스간 동기화에도 사용할 수는 있으나 리눅스에서는 잘 사용하지 않고 쓰레드간 동기화에 사용한다. 이 mutex 관련 함수는 다음과 같다.

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr );
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr )

mutex 를 생성하고 초기화 한다.

pthread_mutexattr_t : mutex 생성시 속성 값으로 { FAST, RECURSIVE, ERROR CHECKING } 이 있으며 보통 NULL 을 준다.

또한 pthread_mutex_init 함수를 사용하지 않고 상수들을 이용하여 정적으로 초기화 할 수 있다.

pthread_mutex_t   mutex_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t   mutex_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP ;
pthread_mutex_t   mutex_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

 

 

pthread_mutex_lock(pthread_mutex_t *mutex)

lock을 실행한다. 이미 다른곳에서 lock이 된 경우 , unlock 될 때까지 블록킹 된다.

 

pthread_mutex_trylock(pthread_mutex_t *mutex)

lock을 실행한다. 이미 다른곳에서 lock이 된경우, EBUSY를 리턴 한다.

 

 

pthread_mutex_unlock(pthread_mutex_t *mutex)

unlock을 실행한다.

 

pthread_mutex_destroy(pthread_mutex_t *mutex)

mutex를 삭제한다.

 

 

2. 세마포어(semaphore)

mutex와 유사하지만, mutex는 하나의 접근만 허용하지만 semaphore는 여러개의 접근도 허용하게 해준다. 또한 semaphore는 프로세스 , 쓰레드 에서 사용 될 수 있으며, SYSTEM V, POSIX 로 구분 된다.

 

2.1 SYSTEM V

SYSTEM V 는 전통적 semaphore 방식이다.

int semget(key_t key, int nsems, int semflag);
int semctl(int semid, int semnum, int cmd, union semun arg);
int semop(int semid, struct sembuf *sops, size_t nsops);

 

int semget(key_t key, int nseems, int semflag)

semaphore를 생성한다.

key_t : semaphore 식별 ID 로 IPC_PRIVATE (0 ) 또는 임의의 수, ftok() 함수에서 리턴하는 값으로 사용할수 있다.

nseems : semaphore 집합 갯수

semflag : IPC_CREATE 일 경우 key id 가 없으면 생성하도록 한다. IPC_EXCL 일 경우 key id가 존재 시 실패를 리턴한다.(이 경우 기존 미리 생성된 semaphore를 사용못한다)

 

semctl(int semid, int semnum, int cmd, union semun arg )

semaphore를 제어한다.

semid: semaphore 식별 ID

semnum : semaphore 집합내의 위치

cmd : 제어 명령

GETVAL : semaphore 값을 구한다.
GETPID : semaphore에 가장 최근에 접근했던 프로세서 ID 구한다.
GETNCNT : semaphore값이 증가하기를 기다리는 프로세스의 갯수
GETZCNT : semaphore값이 0이 되기를 기다리는 프로세스의 갯수
GETALL : semaphore 집합의 모든 semaphore 값을 구한다.
SETVAL : semaphore 값을 설정한다.
SETALL : semaphore 집합의 모든 semaphore 값을 설정
IPC_STAT : 정보를 구한다.
IPC_SET : semaphore 소유권과 접근허가를 설정한다.
IPC_RMID : semaphore 집합을 삭제한다.

arg: semaphore의 값을 설정하거라 값을 얻을 때 사용하는 변수

union semun{
   int                  val;
   struct   semid_ds   *buf;
   unsigned short int  *arrary;
}

 

semop(int semid, struct sembuf *sops, size_t nsops)

semaphore값을 변경하는 함수이다.

semid: semaphore 식별 ID

sops : semaphore 설정값 변경을 위한 값

struct sembuf {
    short sem_num;   semaphore  집합 번호
    short sem_op;     semaphore 증감값
    short sem_flg;     옵션(IPC_NOWAIT : 호출 시 실행 못했을 때 실패 리턴. SEM_UNDO : 프로세서 종료시 semaphore 설정을 원래 상태로 복귀 하는 옵션. 보통 이 옵션 사용) 
}

nsops : 변경하려는 갯수

 

2.2 POSIX semaphore

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t* sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
int sem_destroy(sem_t *sem);

sem_init(sem_t *sem, int pshared, unsigned int value)

semaphore 값을 value로 초기화 한다.

pshared : 0 이면 현 프로세스 에서 사용, 그외에는 여러 프로세스 에서 사용



sem_wait(sem_t *sem)

semaphore 값을 1 줄이고 , 0 일 경우 대기한다.



sem_trywait(sem_t *sem)

sem_wait와 동일한 기능을 하며 0일 경우 EAGAIN을 리턴한다.



sem_post(sem_t *sem)

semaphore 값을 1 증가 시킨다.



sem_getvalue(sem_t *sem, int *sval)

semaphore 값을 sval에 저장한다.



sem_destroy(sem_t *sem )

semaphore 삭제하고 해제한다.

리눅스에서 프로그래밍 하다 보면 꼭 필요한 유틸리티이다. make 유틸은

소스관리를 돕고 컴파일 되어야 하는 소스 파일도 관리한다.

main.c , sub.c , ui.c test.c 라는 파일이 있을 때 보통

gcc –o main main.c sub.c ui.c test.c

이렇게 컴파일 한다. 이걸 일일이 타이핑 하던지 쉘 스크립트로 구성해서

할 수 있지만, 그렇게 편한 방법은 아니다. 그래서 make 유틸을 사용하여

Makefile 을 구성하면 좀더 편안한 구성이 될수 있다.

1. 일반적 사용 예

main: main.o sub.o ui.o test.o

gcc –o main main.o sub.o ui.o test.o

main.o: main.c

gcc –c main.c

sub.o: sub.c

gcc –c sub.c

ui.o: ui.c

gcc –c ui.c

test.o: test.c

gcc –c test.c

위는 Makefile로 구성한 컴파일 예이다.

한 라인씩 간단하게 설명해 보면,

1. main 이 구성되기 위해선 main.o sub.o ui.o test.o 오브젝트 파일이 필요로 한다.

파일이 없다면, 각 파일 구성 규칙으로 이동한다.

2. 각 파일 구성 규칙은 단순하게 gcc 의 c 옵션으로 오브젝트파일만을 생성한다.

3. 모든 오브젝트 파일이 존재하게 되면 main 구성 규칙으로 이동하여

Linking을 하게 된다.

2. 매크로 사용

OBJECT = main.o sub.o ui.o test.o

main: $(OBJECT)

gcc –o main main.o sub.o ui.o test.o

main.o: main.c

gcc –c main.c

sub.o: sub.c

gcc –c sub.c

ui.o: ui.c

gcc –c ui.c

test.o: test.c

gcc –c test.c

매크로를 사용하여 좀더 간단하게 구성할 수 있다. 사용법 또한 일반 변수에

값을 대입 하듯이 사용하면 되고, 사용법은 $(매크로이름) 과 같이 사용한다.

또한 make 유틸에는 내부 매크로가 있다. 이는 좀더 Makefile 구성을

간략하게 생략하게 한다든지 확장을 위해서 있는 매크로로 몇몇개 자주쓰는

매크로 위주로 간략한 설명만 하도록 하겠다.

$* 확장자가 없는 현재의 목표 이름

main.o: main.c
            gcc –c $*.c
$* 는 목표 이름 main.o 에서 main 이 된다.
$@ 현재 목표 이름
main: $(OBJECT)
            gcc –o $@ main.o sub.o ui.o test.o
$@ 는 목표 이름 main 이 된다.
$< 조건 중 첫번째 이름

main.o: main.c test.h
         gcc –c $<
$<는 main.c test.h 중 첫번째 main.c가 선택된다.
$? 현재의 목표 이름 보다 더 최근에 갱신된 이름

main.o: main.c test.h
         gcc –c $?
test.h가 수정되었다면 test.h가 선택
$^ 조건에 있는 내용을 치환

main: main.c test.h 
       gcc –o $^
$^는 main.c test.h 를 뜻한다.

 

* 코드

-o : 출력 파일명을 정하는 옵션

예) test.c 를 컴파일 하여 execute 실행 파일로 만든다.

$ gcc -o execute test.c

-c : 컴파일만 하는 옵션

예) test.c를 컴파일 한다. (test.o 오브젝트 파일 생성)

$ gcc –c test.c

-I : 소스에서 include 하는 헤더의 위치를 지정한다.

예) 위 소스에서 lib.h 파일이 /home/test/include 에 존재 시.

$ gcc –o execute test.c –I/home/test/include

-L  : 소스에서 사용하는 라이브러리 파일이 있는 위치를 지정한다.

예) 위 소스에서 libtest.a 파일이 /home/test/lib 에 존재 시

$ gcc –o execute test.c –I/home/test/include –L/home/test/lib

-l(소문자 L): 소스에서 사용하는 라이브러리 파일을 명시.

예) 위 소스에서 libtest.a 파일을 명시한다. 이때 lib 와 .a 는 생략한다.

$ gcc –o execute test.c –I/home/test/include –L/home/test/lib –ltest


'프로그래밍 > 리눅스 드라이버' 카테고리의 다른 글

쓰레드 동기화(synchronization)  (0) 2013.08.14
make 간단 설명  (0) 2013.08.14
GCC 옵션 간단하게 정리하기  (0) 2013.08.14
모듈 프로그래밍 #2  (0) 2013.08.14
모듈 프로그래밍 #1  (0) 2013.08.14
I/O Method  (0) 2013.08.14

모듈을 커널에 올릴때 insmod 명령어를 사용한다고 앞 모듈 프로그래밍 #1 문서에서 간략하게 설명했다. (솔직히 부실하다.. 추후 좀더 자세하게 모듈이 커널에 올라가는 원리에 대해서 커널 소스를 분석해서 설명해 봐야 겠다)

insmod 명령어가 실행되면 커널은 해당 모듈의 초기화 함수를 호출한다. 또한 rmmod 로 커널에서 모듈을 삭제시는 모듈 삭제 함수가 호출된다.

초기화 함수, 삭제 함수는 모듈 소스에서

module_init

module_exit

이 두 매크로 함수에 정의해준다. 매크로 함수명을 보면 대충 이해갈수 있겠지만 module_init은 초기화 함수, module_exit는 모듈을 커널에서 삭제시 호출된다.

static int hello_module_init(void) 
{ 
	printk("hi~\n"); 
	return 0; 
} 

static int hello_module__exit(void) 
{ 
	printk("I'm exit\n"); 
} 

module_init(hello_init); 
module_exit(hello_exit); 
	

위와 같은 방법으로 매크로 함수에 init 함수와 exit 함수를 등록한다.

'프로그래밍 > 리눅스 드라이버' 카테고리의 다른 글

쓰레드 동기화(synchronization)  (0) 2013.08.14
make 간단 설명  (0) 2013.08.14
GCC 옵션 간단하게 정리하기  (0) 2013.08.14
모듈 프로그래밍 #2  (0) 2013.08.14
모듈 프로그래밍 #1  (0) 2013.08.14
I/O Method  (0) 2013.08.14

  커널의 모듈(Module)은 리눅스 커널안에 포함되지 않고 필요시 동적으로 커널과 결합해서 사용하는 커널 프로그램이다. 이 말은 커널을 동적으로 변경으로 필요시 바꿀수 있다는 말과 일치하게 되며, 메모리의 공간적 여유도 확보에 유리할수 있다. (필요 없는 것들은 동작안시키면 되니깐...) 하지만 동적으로 커널과 연결되다 보니 초기 연결시 시간이 오래 걸리고 , 커널 모드 프로그램이므로 잘못된 모듈일 경우 심각한 오류를 발생시킬수 있다.

  모듈을 커널에서 올리고 내리는 명령어는 각각 insmod 와 rmmod 이다. 뭐 리눅스 명령어다 보니 manpage 나 이런거 참조하면 사용방법에 대해서 알수 있을것이다. 모듈이 커널에 올라가게 되면 모듈에서 다른 커널이나 모듈에 제공하는 함수들을 등록한다. 모듈이나 커널에서 제공하는 모든 함수 리스트는 /proc/kallsyms 에서 확인하면 된다.

cat /proc/kallsyms
[함수주소] [함수종류] [함수이름]
0xFFFC420 T Register_Driver

위와 같은 형태로 출력된다.

'프로그래밍 > 리눅스 드라이버' 카테고리의 다른 글

쓰레드 동기화(synchronization)  (0) 2013.08.14
make 간단 설명  (0) 2013.08.14
GCC 옵션 간단하게 정리하기  (0) 2013.08.14
모듈 프로그래밍 #2  (0) 2013.08.14
모듈 프로그래밍 #1  (0) 2013.08.14
I/O Method  (0) 2013.08.14

디바이스 레지스터(Device Register)들에 대한 접근 방법은 Memory-Mapped I/OI/O Mapped I/O 가 있다.

Memory-Mapped I/O : 메모리의 일부 공간을 디바이스의 레지스터 공간으로 mapping 한다.

I/O mapped I/O: 메모리와 I/O 영역이 별도의 공간의 저장되어 Memory-Mapped I/O처럼 메모리 용량이 줄어드는 문제는 없어진다. 메모리에 대한 데이터 Access는 Load, Store 로 이뤄지며, I/O 입출력은 Input, Output 으로 이뤄진다.


Memory-Mapped I/O의 레지스터 접근 예)

violatile dword *PMM;
PMM = (volatile dword *)0x000111F3

I/O Mapped I/O 레지스터 접근 )

unsigned inb( unsigned port);
void outb(unsigned char byte, unsigned port);

 

'프로그래밍 > 리눅스 드라이버' 카테고리의 다른 글

쓰레드 동기화(synchronization)  (0) 2013.08.14
make 간단 설명  (0) 2013.08.14
GCC 옵션 간단하게 정리하기  (0) 2013.08.14
모듈 프로그래밍 #2  (0) 2013.08.14
모듈 프로그래밍 #1  (0) 2013.08.14
I/O Method  (0) 2013.08.14

+ Recent posts