2015.05.13 20:01


Table of Content
  1. Intro
  2. 쉬운 API
  3. 어려운  API
  4. 어려운 API 와 쉬운 API?
  5. CPU 소모성 API
  6. CPU 소모가 없는 API
  7. msleep()
  8. msleep_interruptible()
  9. ssleep()
  10. schedule_timeout_interrupt()
  11. schedule_timeout_uninterrupt() 
  12. schedule_timeout() 
  13. ndelay(), udelay(), mdelay()  


1. Intro
 커널에서 시간 지연 함수는 여러가지가 있습니다. 
 굳이 분류를 하자면 쉬운 API, 어려운 API, CPU 소모성 API, CPU 소모가 없는 API 정도로 나눌 수 있습니다. 

2. 쉬운 API
 쉬운 API는 크게 보면 아래와 같습니다. 
  #include <linux/delay.h>
  #include <linux/timer.h>

  msleep()
  ssleep()

 이 API들은 함수명대로 micro  second, milli second, 1 second 을 뜻합니다. 
 그냥 사용하시면 됩니다. 

3. 어려운 API
 어려운 API 는 크게 보면 아래와 같습니다. 

 
  #include <linux/delay.h>
   #include <linux/timer.h>
   
   msleep_interruptible()
   schedule_timeout_interruptible();
   schedule_timerout_uninterruptible();
   schedule_timeout();

 이것도 그냥 사용하면 됩니다.

4. 어려운 API 와 쉬운 API ?
  어려운 API 와 쉬운 API 의 차이점은 무엇일까요? 

  가장 기본이 되는 지연 함수는 schedule_timeout() 입니다. 
  관계를 살펴보면

      ssleep()
    -----------------------------------------
      msleep()

    -----------------------------------------
      schedule_timeout_interruptible(), schedule_timeout_uninterruptible
 
    -----------------------------------------
      schedule_timeout()

 입니다. 위에 그림만으로는 잘 설명이 안될 수도 있는데, 커널 소스를 살펴보면 관계를 확실히 확인 할 수 있습니다. 

  ssleep() 는 msleep() 를 호출합니다. 
  msleep() 는 schedule_timeout_uninterrupt() 를 호출합니다. 
  schedule_timeout_uninterrupt() 는 schedule_timeout() 을 호출합니다. 

 결국 최종으로 호출되는 것은 schedule_timeout() 인데 시간 지연이라는게 인터럽트를 허용할 수도, 아닐수도 있고 또한 사용상의 편의상 ssleep() 나 msleep() 같은 front-end 를 만들어 쓴다고 생각하면 됩니다. 

8. CPU 소모성 API
 지정한 지연시간까지 loop 를 돌면서 cpu 시간을 소모하는 형태의 API 입니다. 
 
 ndelay();
 udelay();
 mdelay();

 위와 같이 delay 라는 이름이 붙은 API 들이 CPU 소모성 API 이며 아래와 같이 구현할 경우에도 CPU 소모성 API 입니다. 
 
 while(time_before(jiffies, j1))
 {
   cpu_relax();
 }

9. CPU 소모가 없는 API
 CPU 점유가 없이 시간 지연을 구현하려면 현재 자기자신한테 할당된 프로세스 시간을 반납하고 대기하는 방식을 사용하면 됩니다. 

 schedule_timeout_interruptible()
 schedule_timeout_uninterruptible()

 등이 그런 계통입니다. 

7. msleep()
  header : 
asm/delay.h
  void msleep(unsigned int msecs);
  unsigned long msleep_interruptible(unsigned int msecs);

 => 밀리세컨드 동안 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 없습니다. 

example

 #include <asm/delay.h>

static void example_msleep(void)
{
  msleep(1000);


 
8. msleep_interruptible()
  header : 
asm/delay.h
  unsigned long msleep_interruptible(unsigned int msecs);

 => 밀리세컨드 동안 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 있습니다. 

example)

 #include <asm/delay.h>

static void example_msleep(void)
{
  msleep_interruptible(1000);


 
9. ssleep()
  
  header : asm/delay.h
    void ssleep(unsigned int seconds);

 => 초단위로 지연해 줍니다. 지연시간동안에는 인터럽트를 받을 수 없습니다. 

example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  ssleep(2);



10. schedule_timeout_interruptible()

  
  header : linux/sched.h
    void schedule_timeout_interruptible(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다.  

example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  schedule_timeout_interruptible(1 * HZ); /* 1 초간 지연 */


11. schedule_timeout_uninterruptible()

  
  header : linux/sched.h
    void schedule_timeout_uninterruptible(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다. 지연시간도중에는 인터럽트를 받을 수 없습니다. 
  
  
example)

 #include <linux/sched.h>

static void example_ssleep(void)
{
  schedule_timeout_uninterruptible(1 * HZ); /* 1 초간 지연 */


 
12. schedule_timeout()

  
  header : linux/sched.h
    void schedule_timeout(unsigned long timeout);

 => timeout 시간만큼 지연해 줍니다. 가장 기본이 되는 함수입니다. 그렇기 때문에 직접 호출하여 사용하지는 않습니다. 

example)

 #include <linux/sched.h>

static void __sched schedule_timeout_interruptible(void long timeout)
{
  __set_current_state(TASK_INTERRUPTIBLE);
 
 return schedule_timeout(timeout); 



13. ndelay, udelay, mdelay
   header : linux/delay.h
   #define ndelay(n);
   #define udelay(n); 
   #define mdelay(n);

  busy wait 방식이어서 cpu 로드가 많은 편입니다. 하지만 간단하게 쓸 수 있는 장점이 있습니다. 

example)


#include <linux/delay.h>

void delay_test(void long timeout)
{
  ndelay(1000);
  udelay(1000);
  mdelay(1000); 
 


신고


Posted by injunech
2015.04.06 12:27


쉘 스크립트를 만들때,  가장 첫 라인에 

#!/bin/bash 

를 왜 써야 하는지에 대하여 알아 보도록 하겠습니다.


쉘 스크립트의 가장 첫 라인에  !/bain/bash 를 쓰게 됨으로 해서, 내가 사용 하려는 명령어 해석기가 bash 쉘 임을 미리 알려주는 것입니다.


일반적으로 스크립트에서 #는 주석기호이지만, 첫라인의 #!/bin/bash 에서의 #은 주석기호가 아닙니다.


스크립트의 가장 첫라인에 있는 #! 은 스크립트의 제일 앞에서 이 파일이 어떤 명령어 해석기의 명령어 집합인지를 시스템에게 알려주는 역할을 합니다.


#! 은 두 바이트의 "매직넘버"("magic number")로서, 실행 가능한 쉘 스크립트라는 것을 나타내는 특별한 표시자입니다.


#! 바로 뒤에 나오는 것은 경로명으로, 스크립트에 들어있는 명령어들을 해석할 프로그램의 위치를 나타내는데 그 프로그램이 쉘인지, 프로그램 언어인지, 유틸리티인지를 나타냅니다. 


이 명령어 해석기가 주석은 무시하면서 스크립트의 첫 번째 줄부터 명령어들을 실행시킵니다. 


거의 대부분의 상업용 유닉스 및 리눅스 에서 기본 본쉘인 #!/bin/sh을 쓰면 비록 Bash 만 가지고 있는 몇몇 기능들을 못 쓰게 

되겠지만 리눅스가 아닌 다른 머신에 쉽게 포팅 할 수 있게 해 줍니다. (이렇게 작성된 스크립트는 POSIX sh 표준을 따르게 됩니다. )

"#!" 뒤에 나오는 경로는 정확히 Full PATH를 기록 해야 합니다. 

만약 PATH를 잘못 적게 되면, 스크립트를 돌렸을 때 거의 대부분 "Command not found"라는 에러 메세지만 보게 될 것입니다.


이상으로 쉘 스크립트 처음에 왜 "#!/bin/bash"를 반드시 써야만 하는지에 대하여 알아 보았습니다.

신고


Posted by injunech
2015.03.26 13:45


디바이스 드라이버 작업을 하다 보면 자주 보게 되는 매크로중으 히나가

바로  container_of 매크로 입니다.

이해가 가는 것 같기도 하고 아니것 같기도 하고 묻는 사람도 있고 해서

오늘은 이 매크로에 대해서 한번 알아 볼가 합니다.


일단 함수 원형을 한번 볼까요?


lxr 사이트에서 긁어 왔습니다.

위치는 include/linux/kernel.h 입니다.


 650#define container_of(ptr, type, member) ({                      \
 651        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
 652        (type *)( (char *)__mptr - offsetof(type,member) );})


이건 뭐 상당히 거시기 하군요.

뜯기 전에 좀 쉬운 예를 들어 보겠습니다.


 




위의 그림이 보이십니까?

뽀글이 structure 는 인자가 4개 입니다.

시작 번지는 1000 번지구요.  

라면 번지 1000

수프 번지 1008

가격 번지 1016

봉지 번지 1020


이렇게 되는 군요.

그런데 어떤 함수를 호출하면서 가격의 포인터를 넘겨주었습니다.


그래서...  가격포인터를 인자로 받은 함수가 아래와 같습니다.


int   how_much( short *가격)

{

     printk(" 가격위치는 0x%p\n", 가격)

     return 0;

}


자 이제 how_much 함수는 가격포인터를 프린트해줍니다.

당연히 그 값은 1016 번지가 되겠죠.


자 이제 how_much 함수는 가격의 포인터를 알고 있습니다.

처음 그림에서 보면 가격 인자는 뽀글이 자료 구조의 멤버네요?


how_much 에서 short * 형태로 인자를 받았지만

그 포인터가 struct 뽀글이 멤버라는 것을 알고 있고

struct 뽀글이 다른 멤버를 필요로 하는 경우가 생겼습니다.


쉽게 얘기해서 내가 받은 인자를 이용해서 그 인자를 포함하고 있는

자료구조가 필요한 것입니다.


직관적으로 그림에서 보면 뽀글이 자료구조의 주소는 1000 번지 입니다.

가격 포인터가 1016 이므로 16 만 빼면 1000 번지가 나오죠?

이제 우리는 struct 뽀글이 의 변수 위치를 찾은겁니다.


함수를 다시 써보죠.


int   how_much( short *내가격)

{

     struct 뽀글이 *myboggle = containerof(내가격, struct 뽀글이, 가격);


     printk(" 내가격을 멤버로 갖는 boggle 구조체의 위치는 0x%p\n", myboggle)


     myboggle->라면 = 봉지라면;


     return 0;

}


이 결과는 0x1000 이 나오게 됩니다.


컨테이너 매크로에서 필요한 것은 세가지 조건입니다.

현재 알고 있는 변수포인터.

구조체함수원형.

구조체멤버의 변수명.


복잡해 보이기도 하지만 결론적으로는


1000 = container_of(1006, struct 뽀글이, 가격)


이렇게 되는데 1006 이라는 위치를 가르쳐주고

1006 이 뽀글이의 가격이라는 멤버의 주소이다.


뽀글이 구조체에서 가격의 변수 위치는 항상 6이라는 오프셋이니까

1006 - 6 = 1000.


이렇게 되도록 짜여져 있는 매크로 입니다.


복잡한가요?


정리합니다.

container_of (prt, type, member) 

prt           :  임의의 포인터값.

type         :  ptr 을 멤버로 갖는 자료구조형(구하고자 하는 자료구조의 형).

member    :  자료구조형에서 ptr 의 멤머 이름.




출처 : http://forum.falinux.com/zbxe/index.php?document_srl=531954

신고


Posted by injunech
2015.03.13 17:16



모듈과 커널 버전

모듈은 커널 버전과 관련이 많습니다. 커널 버전에 맞춰서 모듈이 빌드되며, 커널의 버전과 모듈의 버전이 일치해야만 이용할 수 있습니다. 커널 소스에서 커널의 버전은 include /linux/version.h에서 확인할 수 있습니다.

#define UTS_RELEASE "2.6.14.6"
#define LINUX_VERSION_CODE 132622
#define KERNEL_VERSION(a,b,c) (((a) << 16 ) + ((b) <<8)  +  (c))

따라서, 특정 버전 이상에서만 동작하는 모듈이라던가, 커널 버전에 따라 다르게 동작하는 모듈을 작성하고 싶다면 이를 활용해서 다음과 같은 코드를 작성할 수 있습니다.

#if LINUX_VERSION_CODE >= KERNEL_VERSION( 2, 6, 10)
MODULE_PARM( user_name, "charp");
#else
MODUILE_PARM( user_name, "s");
#elseif

MODULE_PARM()의 의미나 사용법은 나중에 설명을 한다. 여기서는 VERSION_CODE와 KERNEL_VERSION()사용한 방식만 이해하면 됩니다. KERNEL_VERSION()은 커널 버전 번호를 받아서 정수값으로 만들어 줍니다.  해당하는 커널 버전을 미리 정수 값으로 만들어둔 것이 LINUX_VERSION_CODE이고, 이것은 모든 커널에 선언되어 있습니다.

모듈과 static 선언

모듈 안에서만 사용되는 변수나 함수에는 static 키워드가 선언되어 있습니다. static은 메모리 내에 정적으로 위치하게 만드는 할을 하기 때문에 해당 소스 코드 내에서 전역으로 사용될 수 있습니다. 변수에 사용되면 전역변수가 되며, 함수에 사용되면 전역 함수가 되겟지요. 실제로 .c 코드 안에서 사용되는 모든 C 함수나 함수 바깥에 선언된 변수는 어디서나 상ㅇ할 수 있기 때문에 어떤 의미로는 항상 전역이라고 할 수 있습니다. 함수나 전역 변수에 사용된 static 의 다른 의미는 다른 .c소스 코드에서 접근할 수 없게하는 범위 지정자 입니다.

리눅스 커널은 커널 전체가 하나의 프로그램입니다. 그러니까 같은 이름을 가진 변수나 함수를 만들 수 없습니다. 누군가 A라는 모듈에서 hello()함수를 작성해서 커널 영역에 로드했다면, 또 다른 hello() 함수를 가진 모듈 B는 로드 할 수 없게 됩니다. 이 함수가 모듈 안에서만 사용되고, 다른 커널에서 참조할 필요가 전혀 없다면 이런 파일들은 커널 영역에서 보이지 않게 해야 합니다. 때문에 static을 사용합니다. init_module()과 cleanup_module()함수는 다소 예외적인 경우로는 이 함수는 static을 사용하지 않아도 커널 영역에 공개 되지 않습니다. 실제로, 이들 함수는 __init_module_inline()과 __exit_module() 함수는 다소 예외적인 경우로 이 함수는 static을 사용하지 않아도 커널 영역에 공개 되지 않습니다.  실제로 이들 함수는 __init_module_inline()과 __exit_module_inline() 같은 인라인 함수로 대체되어 실행되고, 이들 함수는 모두 static으로 선언되어 있기 때문에 커널 영역에서 공개되지 않습니다. 커널 영역에 공개된 함수는 /proc/ksyms(2.4커널) 이나 /proc/kallsysms(2.6커널)에서 확인 할 수 있습니다.

typedef int (*__init_module_fun_t)(void)
                            └ init_module()의 원형
typedef void (*__cleanup_module_func_t)(void);
#define module_init(x) \              └ cleanup_module()의 원형
   int init_module(void) __attribute__(alias(#3))); \
   static inline _init_module_func_t __init_module_inline(void) \
    └  커널 영역에 고액되지 않는 지역 함수로 선언됩니다.
   {  return x; }

#define module_exit (x) \
  void cleanup_module(void) __attribute__((alias(#x))); \

  static inline __cleanup_module_func_t__cleanup_module_inline(void) \
    └  커널 영역에 고액되지 않는 지역 함수로 선언됩니다.
  { return x; }
static으로 선언되어 있으면, 외부 C 소스 코드에서 extern 선언으로 참조할 수 없습ㄴ디ㅏ. 2.5 커널에서는 모듈 내에서 static 없이 선언된 함수는 커널 영역에 공개되었지만 2.6에서는 공개되지 않았습니다.

모듈과 커널 심볼

모듈은 커널 영역에서 실행되기 때문에 표준 C 함수들은 사용할 수 없습니다. 대신에, 커널 영역에서 사용할 수 있는 함수나 자료 구조의 목록을 커널에서 제공합니다. 이 목록은 2.4 커널에서는 /proc/ksyms, 2.6 커널에서는 /proc/kallsyms를 통해 제공합니다. printk()함수도 커널 심볼 파일에서 확인할 수 있습니다.

2.6 커널의 심볼

# grep printk /proc/kallsyms
.........     ┌ 심폴의 타입 정보
c011d6b0  T printk
c011d6b0  T vprintk
c011d6b0  T __printk_ratelimit
........                               ┌ 심폴의 타입 정보
c011d6b0  U printk        [vmhgfs]
c011d6b0  U printk        [vmxnet]
      |            └ 심볼 이름
                 └ 심볼 이름

심볼 타입

설명

A

절대 주소로 변경되지 않음

B

bss 영역의 심볼

C

공용 심볼로 초기화되지 않은 변수를 의미

D

data 영역의 심볼로 초기화된 변수를 의미

R

rdata 영역, 즉 읽기 전용변수를 의미

T

text 영역의 심볼로 함수를 의미

U

해당 모듈에서 정의하지 않은 (undefined) 심폴을 의미

I

다른 심볼에 대한 간접(Indirect) 참조를 의미하는 GNU 확장

N

디버깅 심볼

- 심볼의 타입 -

printk() 함수를 사용하는 모듈이 있으면 printk()함수를 이용하는 모듈 이름과 함께 printk()에 대한 심볼 타입이 "U"로 나타납니다. 직접 작성한 모듈에서 printk() 함수를 정의한 것이 아니기 떄문에 그렇습니다. 심볼 타입이 대문자로 사용되면 전역 심볼을 의미하고, 소문자로 되어 있으면 지역 심볼을 의미합니다.


심볼 : 2.4와 2.6의 차이

2.4 커널에서는 모듈 안에서 static으로 선언되지 않은 함수들이 모두 커널 심볼로 공개되어 있습니다. 2.6커널에서는 static으로 선언되지 않은 함수여도, 커널 심볼로 등록 하지 않으면 공개되지 않게 변경되었습니다.

커널 심볼로 공개시키는 방법은 EXPORT_SYMBL()이나 EXPORT_SYMBOL_GPL()과 같은 매크로를 사용해서 공개할 심볼을 지정하기만 하면 됩니다. printk() 함수의 경우 kernel/printk.c 에서 다음과 같은 부분을 확인할 수 있습니다.

kernel/printk.c

EXPORT_SYMBOL(printk);

이와 같이 정의되어 있기 때문에 외부 심볼로 공개되어 있으며, 커널이나 모듈 내에서 자유롭게 사용할 수 있습니다.

2.4 커널의 심볼
[/usr/src/linux-2.4.x.x #] grep printk /proc/ksyms

       ┌ 커널 내 심볼의 시작 주소
c0114aa0 printk_R1b7d4074
                       
└ 커널 버전과 심볼의 CRC 정보를 합한 값

2.6 커널의 /proc/kallsyms에서는 버전 정보를 제공하지 않습니다.
심볼과 관련된 매크로는 include/linux/module.h에 정의되어 있으며 알아야 될 것은 다음과 같습니다.

종류

설명

EXPORT_SYMBOL(var)

심볼을 공개합니다.

EXPORT_SYMBOL_NOVERS(var)

심볼을 버전정보 없이 공개합니다.

EXPORT_SYMBOL_GPL(var)

EXPORT_SYMBOL과 동일 하지만 GPL과 호환되는 라이선스로 등록한 모듈에서만 심볼을 볼 수 있습니다.

EXPORT_NO_SYMBOLS

모듈 내에서 어떤 심볼도 공개하지 않음을 정의합니다.

 - 심볼 매크로  -

EXPORT_NO_SYMBOLS는 모듀 내의 어디서나 한 줄만 넣어주면 되며, 해당 모듈에서 어떤 심볼도 공개하지 않음을 의미합니다. EXPORT_SYMBOL_GPL()은 MODULE_LICENSE()과 관련이 깊습니다.

모듈의 라이선스

리눅스 커널은 GPL라이선스를 따릅니다. 그리고 GPL 라이선스가 적용된 소스 코드를 수정한 경우 수정된 소스 코드를 공개해야 합니다. 일부 업체들은 오픈 소스를 이용해서 제품을 개발하지만 수정된 소스 코드를 공개해야 합니다. 일부 업체들은 오픈 소스를 이용해서 제품을 개발하지만 수정된 소스 코드를 공개하지 않아 물의를 사기도 했습니다. 매년 라이선스와 관련된 분쟁이 증가하고 있습니다. 때문에 모듈에서도 제작자가 원하는 라이선스를 지정하도록 했으면, 이 라이선스를 지정하지 않으면, 오픈소스인 커널에 상업적인 코드가 추가될 수 있고, 이는 커널의 오픈 소스를 위배할 수 있다는 경고를 보여줍니다.

module license 'unspecified' taints kernel.

현재 까지는 MODULE_LICENSE()를 통해 라이선스를 명시하지 않아도 문제는 없지만, 이런 라이선스 논쟁 이후 EXPORT_SYMBOL_GPL()이 추가 되었습니다. 이는, GPL과 호환되는 라이선스를 지정하지 않은 모듈에서는 커널의 기능을 사용할 수 없게 하는 정책을 추가하기 위한 것으로, 현재 이와 관련된 불이익은 없지만, 필요한 경우 GPL 호환 라이선스를 따르지 않는 상업적 코드들은 GPL코드를 이용할 수 없게 하기 위한 조치를 반영한것입니다.

MODULE_LICENSE()를 통해 지저앟ㄹ 수 있는 라이선스는 다음과 같다.

라이선스 종류

의미

GPL

GNU Public License v2 또는 이상

GPL v2

GNU Public License v2

GPL and additional rights

GNU Public License v2 right and more

Dual BSD/GPL

GNU Public License v2 또는 BSD 라이선스 선택

DUAL MPL/GPL

GPL v2 또는 모질라 라이선스 선택

Proprietary

비자유 소프트웨어



출처 : 리눅스 커널 프로그래밍  저 한동훈,원일용, 하홍준  / 한빛 미디어

신고

'Programming > Linux/Unix' 카테고리의 다른 글

디바이스 트리 작성법 (2편)  (1) 2015.03.24
디바이스 트리 작성법 (1편)  (0) 2015.03.24
[Linux] Module 관련  (0) 2015.03.13
[Makefile] Kbuild 와 Module Compile  (0) 2015.03.09
[Makefile] Device Driver  (0) 2015.03.09
Makefile 문법 및 관련 내용  (0) 2015.03.09


Posted by injunech

티스토리 툴바