= Kernel API 1 - Core (1/2) = -- [김도집] [[DateTime(2005-09-22T05:20:31)]] Kernel API에 대한 첫번째 문서이다. * [wiki:KernelApi Kernel API 1 - Core(1/2)] * [wiki:KernelApi1 Kernel API 1 - Core(2/2)] * [wiki:KernelApi2 Kernel API 2 - Extension] * [wiki:KernelApi3 Kernel API 3 - Linux Driver Model] * [wiki:KernelApi4 Kernel API 4 - Porting(ARM)] * [wiki:KernelApi5 Kernel API 5 - Linux Driver Model(ext)] * [wiki:KernelApi6 Kernel API 6 - PM] 본 장에서 다루는 다음과 같다. * 서론 * 기본 자료형 * 주요 헤더 파일들 * initcall/exitcall * 디바이스 등록 및 해제 * 드라이버 등록 및 해제 * 문자 디바이스 드라이버 * 장치 번호 * 프로세스 * 타임(타이머) * 메모리 * MMAP * DMA [[TableOfContents]] == 서론 == 본 내용은 리눅스 커널 내에서 자주 사용되는 API를 중심으로 열거식으로 정리한 것이다. 유기적인 기능들에 대해서는 설명하지 않으므로 이러한 것을 원한다면 기존의 서적들이나 문서들을 참고하기 바란다. 또한 본 내용은 직접 사용하거나 이럴 거 같다는 유추 식으로 작성된 내용도 있으므로 이것이 정확하다고 말할 수는 없다. 그러나 나름대로는 오류가 없도록 할 것이며 잘못된 내용이 있다면 최대한 빨리 수정할 수 있도록 할 것이다. 커널 버전은 2.6.11 또는 그 이후 버전들을 기준으로 작성하였다. 커널의 API는 고정된 것이 아니라 버전별로 조금씩 변경될 수도 있다. 커널 API에 대한 최신의 정보는 [http://lwn.net]에서 확인할 수 있다. - 2005.9.28 김도집 - == 기본 자료형 == 리눅스 커널에서 사용하는 기본 자료형이 있다. 이들에 대한 정의는 에 정의되어 있다. 자주 사용하는 몇 가지 자료형만 정리하면 다음과 같다. ||'''type'''||'''설명'''|| ||u8||unsigned 8비트|| ||u16||unsigned 16비트|| ||u32||unsigned 32비트|| ||s8||signed 8비트|| ||s16||signed 16비트|| ||s32||signed 32비트|| ||size_t||unsigned long|| ||ssize_t||signed long|| ||off_t||long|| == 주요 헤더 파일들 == 커널내에서 프로그래밍을 하는 경우 그 기능에 따라 포함되어야 하는 헤더는 달라진다. 그러나 몇가지는 자주 사용되는데, 이들 헤더에 대한 것을 간략히 소개한다. ||'''헤더파일'''||'''설명'''|| ||'''linux/module.h'''||모듈 드라이버 작성시 반드시 포함|| ||'''linux/kernel.h'''||커널내 코드 작성시 반드시 포함|| ||'''linux/init.h'''||커널 및 모듈 init/exit 관련 코드 포함시 반드시 포함|| ||linux/miscdevice.h||miscellaneous 디바이스 작성시 반드시 포함|| ||linux/device.h||디바이스 등록시 포함|| ||linux/platform_device.h||플랫폼 디바이스 등록시 포함|| ||linux/fs.h||파일 시스템 관련 코드 사용시 포함|| ||linux/slab.h||메모리 관련 코드 사용시 포함|| ||linux/spinlock.h||스핀락 코드 사용시 포함|| ||linux/proc_fs.h||proc 사용시 포함|| ||asm/uaccess.h||사용자 메모리 공간 사용시 포함|| == initcall/exitcall == 리눅스 커널이 부팅 하면서 start_kernel()->do_basic_setup()->do_initalls()를 통해 커널 코드에서 등록된 모든 initcalls 을 실행하게 된다. 경우에 따라서는 initcalls를 통해 등록된 것이 exitcalls를 통해 해제되는 경우도 있다. 이는 initcall과 달리 커널 내에서 일괄처리하지 않고 개별 코드에서 처리하게 된다. 이들 initcall 및 exitcall에 대한 정의는 에 정의되어 있다. 커널에서는 initcall에 대해 7단계의 실행 순서를 정해두고 있다. 각 단계의 번호가 앞 번호일 수도록 다른 큰 번호의 initcall보다 먼저 실행되는 것을 보장한다. ||'''단계'''||'''initcall'''|| ||1||core_initcall(fn)|| ||2||postcore_initcall(fn)|| ||3||arch_initcall(fn)|| ||4||subsys_initcall(fn)|| ||5||fs_initcall(fn)|| ||6||device_initcall(fn), __initcall(fn), module_init(fn)|| ||7||late_initcall(fn)|| exitcall의 경우엔 module_exit(fn), __exitcall(fn) 외엔 특별히 어떤 순서를 갖고 있지 않다. 내부적인 내용에 대한 보다 상세한 내용은 및 arch/xxx/kernel 내의 vmlinux.lds.S를 참고하기 바란다. == 디바이스 등록 및 해제 == 리눅스 커널 2.6에서는 kobject라는 객체 기반으로 디바이스 및 드라이버의 계층적 구조를 갖고 있다. 예를 들어 등록된 버스가 있고 버스에 새로운 장치가 장착되면 버스 드라이버가 장치를 탐지하고 이에 맞는 드라이버를 찾아 등록하게 된다. 따라서 버스 상의 디바이스와 드라이버는 기본적으로 이름으로 찾게 된다. 따라서 버스 상에 등록된 디바이스의 이름과 드라이버의 이름이 서로 다르다면 정상적으로 드라이버가 동작하지 않는다. 다음은 platform_bus_type 라는 가상의 플랫폼 버스 상에 새로운 디바이스를 등록하는 방법이다. /!\ 리눅스 커널 2.6.15 이 후 플랫폼 디바이스에 대한 드라이버를 등록을 위하여 platform_driver 구조체를 새로 정의하였다(그 이전까지는 device_driver 구조체를 사용하였다). === struct platform_device === platform_bus_type 라는 가상의 플랫폼 버스 상의 디바이스를 정의하는 구조체는 에 정의되어 있다. 그 구조체는 platform_device 이다. /!\ '''커널 버전 2.6.15 및 이후 버전에서는 에 정의되어 있다.''' 구조체의 원형은 다음과 같다: {{{#!vim c struct platform_device { char *name; u32 id; struct device dev; u32 num_resources; struct resource *resource; }; }}} ||'''필드'''||'''설명'''|| ||name||플랫폼 디바이스 이름|| ||id||여러 개의 플랫폼 디바이스가 있을 경우 id를 통해 구분한다.|| ||dev||디바이스 정보|| ||num_resources||리소스 테이블의 개수|| ||resource||리소스 테이블의 시작 포인터|| id는 동일한 이름의 디바이스가 여러 개를 정의할 필요가 있을 때 각 디바이스에 id를 부여할 때 사용한다. 만약 동일한 이름에 하나의 디바이스만 존재하는 경우엔 id를 -1로 지정한다. 다음은 {{{struct device;}}}에 대한 내용이다. 이는 에 선언되어 있다. {{{#!vim c struct device { ... struct device *parent; struct kobject kboj; char bus_id[BUS_ID_SIZE]; ... struct bus_type *bus; strcut device_driver *driver; ... void (*release)(struct device *dev); }; }}} 간단한 사용 예는 다음과 같다: {{{#!vim c static void xxx_relese_dev(struct device *dev) { } static struct platform_device xxx_device = { .name = "xxx_device", .id = 0, .dev = { .release = xxx_release_dev, }, .num_resources = 0, }; }}} 디바이스에 따라서는 리소스를 필요로 하는 경우가 있다. 이러한 리소스를 정의하는 것이 resource 구조체이다. 이는 에 정의되어 있다. {{{#!vim c struct resource { const char *name; unsigned long start, end; unsigned long flags; struct resource *parent, *sibling, *child; }; }}} 다음은 resource 구조체 내의 각 필드에 대한 설명이다. ||'''필드'''||'''설명'''|| ||name||리소스 이름을 지정한다|| ||start||리소스의 시작 값|| ||end||리소스의 마지막 값|| ||flags||리소스 타입|| ||parent, sibling, child|||| 위에서 flags에서 사용할 수 있는 값이 매크로로 정의되어 있는데 그 값은 IORESOURCE_IO, IORESOURCE_MEM, IORESOURCE_IRQ, IORESOURCE_DMA이다. 리소스를 갖는 플랫폼 디바이스 구조체의 정의는 다음과 같을 것이다. {{{#!vim c static struct resource xxx_resources[] = { { /* registers */ .start = XXX_MEM_BASE, .end = XXX_MEM_BASE + 0xff, .flags = IORESOURCE_MEM, }, { /* general IRQ */ .start = XXX_IRQ_BASE + 20, .flags = IORESOURCE_IRQ, }, { /* PIO IRQ */ .start = XXX_IRQ_BASE + 30, .flags = IORESOURCE_IRQ, }, }; static u64 xxx_dmamask = ~(u32)0; static struct platform_device xxx_device = { .name = "xxx", .id = -1, .dev = { .relase = xxx_release, .dma_mask = &xxx_dmamask, .coherent_dma_mask = 0xffffffff, }, .num_resources = ARRAY_SIZE(xxx_resources), .resource = xxx_resources, }; }}} ==== to_platform_device ==== 실제 드라이버에서 각 함수들은 platform_device 구조체형의 인자 대신 device 구조체형의 인자를 넘겨 받는다. 이 때 device 구조체를 포함하고 있는 platform_device 구조체의 포인터 주소를 구하고자 한다면 to_platform_device 매크로를 사용할 수 있다. 이 매크로는 에 정의되어 있다. {{{#!vim c #define to_platform_device(x) container_of((x), struct platform_device, dev) }}} 간단히 사용예를 보면 다음과 같다. {{{#!vim c static int __init xxx_probe(struct device *dev) { struct platform_device *xxxdev = to_platform_device(dev); ... } }}} ==== platform_device_register ==== 플랫폼 디바이스를 등록한다. [[BR]] 커널 2.6.14: [[BR]] 커널 2.6.15 및 이후 버전: [[BR]] 함수 원형은 다음과 같다: {{{#!vim c int platform_device_register(struct platform_device *pdev) }}} 등록에 성공하면 0을 반환하고 실패시엔 음수를 반환한다. ==== platform_device_unregister ==== 플랫폼 디바이스의 등록을 해제한다.[[BR]] 커널 2.6.14: [[BR]] 커널 2.6.15 및 이후 버전: [[BR]] 함수 원형은 다음과 같다: {{{#!vim c void platform_device_unregister(struct platform_device *pdev) }}} ==== platform_device_register_simple ==== 플랫폼 디바이스를 간단히 등록할 수 있는 함수이다. 함수 원형은 다음과 같다: {{{#!vim c struct platform_device *platform_device_register_simple(char *name, unsigned int id, struct resource *res, unsigned int num); }}} 인자들에 대한 설명은 다음과 같다: ||'''인자'''||'''설명'''|| ||name||추가할 디바이스의 이름|| ||id||인스턴스 id|| ||res||디바이스를 위한 자원에 대한 정보|| ||num||필요한 자원의 수|| id는 여러 디바이스가 있을 경우 이를 구분하기 위한 것이다. 만약 단 하나의 디바이스만 있다면 -1으로 지정한다. 본 함수를 통해 등록된 플랫폼 디바이스는 platform_device_unregister를 통해 등록 해제한다. 유사 함수: platform_device_register [[BR]] 관련 함수: platform_device_unregister 사용 예는 다음과 같다: {{{#!vim c static struct platform_device *xxx_platform_device; static int __init xxx_init(void) { ... xxx_platform_device = platform_device_register_simple("xxx", 0, 0, 0); driver_register(&xxx_drv); ... return 0; } static void __exit xxx_exit(void) { platform_device_unregister(xxx_platform_device); driver_unregister(&xxx_drv); ... } module_init(xxx_init); module_exit(xxx_exit); }}} == 드라이버 등록 및 해제 == 앞에서 설명하는 디바이스 중 플랫폼 디바이스가 있다. 커널 2.6.15에서 플랫폼 디바이스를 위한 드라이버를 위한 구조체 및 API가 새롭게 정의되었다(그 이전까지는 device_driver 구조체 및 그와 관련된 API를 사용했었다). === platform_driver === 앞서 설명한 바와 같이 커널 '''2.6.15'''부터 플랫폼 디바이스에 대한 드라이버를 위한 구조체 및 API가 추가되었다. platform_driver 구조체는 에 선언되어 있으며 그 원형은 다음과 같다: {{{#!vim c struct platform_driver { int (*probe) (struct platform_device *); int (*remove) (struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend) (struct platform_device *, pm_message_t state); int (*resume) (struct platform_device *); struct device_driver driver; }; }}} 플랫폼 드라이버의 등록 및 해제를 위한 함수는 다음과 같다. ||'''함수'''||'''설명'''|| ||platform_driver_register||플랫폼 드라이버를 등록한다.|| ||platfrom_driver_unregister||등록된 플랫폼 드라이버의 등록을 해제한다.|| ==== platform_driver_register ==== 플랫폼 드라이버를 등록하는 함수로 에 선언되어 있다. {{{#!vim c int platform_driver_register(struct platform_driver *drv) }}} 라이선스: GPL 성공시엔 0을 반환하고 실패시에 음수를 반환한다. 다음은 예이다. {{{#!vim c static struct platform_driver xxx_driver = { .probe = xxx_probe, .remove = xxx_remove, .suspend = xxx_suspend, .resume = xxx_resume, .driver = { .name = "xxx", .owner = THIS_MODULE, }, }; static int __init xxx_init(void) { if (platform_driver_register(&xxx_driver)) { printk("failed to register xxx dirver\n"); return -ENODEV; } } }}} ==== platform_driver_unregister ==== 등록된 플랫폼 드라이버의 등록을 해제하는 함수로 에 선언되어 있다. {{{#!vim c void platform_driver_unregister(struct platform_driver *drv) }}} 라이선스: GPL ==== platform_get_drvdata/platform_set_drvdata ==== 플랫폼 드라이버 내의 함수들에서 공통적으로 사용할 필요가 있는 데이터가 있을 것이다. 이런 데이터를 드라이버 구조체내의 정해진 포인터를 이용하여 상호 참조할 수가 있다. 이렇게 참조하기 위해서는 platform_set_drvdata를 통해 포인터를 등록해야 하며, 이렇게 등록된 포인터를 참조할 때는 platform_get_drvdata를 사용한다. 이들은 매크로로 정의되어 있으며 에 선언되어 있다. {{{#!vim c #define platform_get_drvdata(_dev) dev_get_drvdata(&(_dev)->dev) #define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data)) }}} 사용은 다음과 같다. {{{#!vim c static int xxx_probe(struct platform_device *dev) { struct xxx_controller *con = the_controller; ... platform_set_drvdata(dev, con); ... } static int xxx_remove(struct platform_device *dev) { strcut xxx_controller *con = platfrom_get_drvdata(dev); ... } }}} === device_driver === driver_register/device_unregister 함수에서 사용하는 자료형은 에 선언되어 있다. /!\ platform_device에 대한 드라이버 등록은 커널 2.6.15부터는 platform_driver_register를 이용하여 등록한다. {{{#!vim c struct device_driver { const char *name; struct bus_type *bus; struct completion unloaded; struct kobject kobj; struct klist klist_devices; struct klist_node knode_bus; struct module *owner; int (*probe) (struct device *dev); int (*remove) (struct device *dev); int (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state, u32 level); int (*resume) (struct device *dev, u32 level); }; }}} ||'''필드'''||'''설명'''|| ||name||디바이스 드라이버의 이름을 문자열 형으로 지정한다 (필수)|| ||bus||보통은 &platform_bus_type으로 지정한다 (필수)|| ||unloaded|||| ||kobj||내부적으로 드라이버 객체를 관리하기 위한 것으로 직접적으로 사용하지 않는다 (임의 지정하지 않는다)|| ||klist_devices|||| ||knode_bus|||| ||owner|||| ||probe()||디바이스의 초기화 루틴이다 (필수)|| ||remove()||디바이스가 제거될 때 호출되는 루틴이다 (필수)|| ||shutdown()|||| ||suspend()||절전모드로 들어갈 때 호출된다 (필수)|| ||resume()||절전모드를 빠져나올 때 호출된다 (필수)|| /!\ 위 테이블에서 foo()와 같이 ()가 붙은 필드는 함수(포인터)를 의미한다. ==== driver_register ==== 버스를 갖는 드라이버를 등록할 때 사용하는 함수이다. 함수의 원형은 다음과 같다: {{{#!vim c int driver_register(struct device_driver *drv); }}} 관련함수: driver_unregister {{{#!vim c static struct device_driver foo_driver = { .name = "foo", .bus = &platform_bus_type, .probe = foo_probe, .remove = foo_remove, .suspend = foo_suspend, .resume = foo_resume, }; static int __init foo_init(void) { int err; err = driver_register(&foo_driver); return err; } }}} ==== driver_unregister ==== driver_register()를 통해 등록된 드라이버를 해제할 때 사용한다. 함수의 원형은 다음과 같다: {{{#!vim c void driver_unregister(struct device_driver *drv) }}} 관련함수: driver_register ==== dev_set_drvdata ==== 사용자 데이터를 device 구조체의 driver_data 필드를 통해 참조할 수 있도록 설정하는 wrapper 함수이다. 함수 원형은 다음과 같다: {{{#!vim c void dev_set_drvdata(struct device *dev, void *data) }}} 관련함수: dev_get_drvdata ==== dev_get_drvdata ==== device 구조체의 driver_data 필드의 참조 주소를 가져오는 wrapper 함수이다. 함수 원형은 다음과 같다: {{{#!vim c void * dev_get_drvdata(struct device *dev) }}} 관련함수: dev_set_drvdata == 문자 디바이스 드라이버 == 문자 드라이버와 관련된 헤더 파일은 이다. 리눅스 커널에서 지원하는 대표적인 드라이버의 형태는 문자형, 블록형, 네트워크 드라이버 등이 있다. 쉽게 접근할 수 있고 가장 많이 사용되는 것이 문자형 디바이스 드라이버이다. 디바이스 파일 중 ls -l를 통해 접근권한 내용중 crw-rw---- 등과 같이 c로 시작하는 것이 문자형 디바이스 드라이버이다. 커널 버전 2.6으로 넘어오면서 내부적으로 드라이버의 구조는 많은 변화를 겪었다. 그러나 여전히 이전 버전에서 사용하던 API는 그대로 사용 가능하다. 예를 들어 {{{register_chrdev()}}} 등의 문자형 디바이스 드라이버의 등록 함수가 그러하다. 이후에 소개하는 내용은 커널 2.6에서 새롭게 적용된 API를 이용하여 문자 디바이스 드라이버를 구현하는 방법을 소개하는 것이다. 사실, 커널 버전 2.6.11에서도 이들을 직접 사용하는 예는 그리 많지 않다. === 어떤 API를 사용할 것인가 === 앞서 말한 바와 같이 커널 2.6에서는 cdev로 시작하는 새로운 API를 제공하고 있다. 기존의 register_chrdev/unregister_chrdev는 major 번호를 기준으로 minor 번호가 0에서 255까지 일괄적으로 디바이스 자원을 등록/해제한다. 반면에 새 API를 사용하면 디바이스를 등록/해제를 세밀히 할 수 있다. 디바이스의 시작 번호를 주어 이후 몇개를 등록할지를 결정하여 몇개의 minor 번호를 사용할지를 결정할 수 있다. 이 새 API는 cdev_ 또는 alloc_ 로 시작한다. === chrdevs vs cdev_map === 커널 2.4에서 문자와 블록 디바이스는 chrdevs와 blkdevs라는 배열에서 관리하였다. 그러나 커널 2.6에서는 이를 kobj_map형인 cdev_map이 대신하고 있다. 단, chrdevs는 남아 있는데, 이는 문자 디바이스의 사용 가능 여부를 판별하는 정도로 그 기능이 축소되었다. === 문자 디바이스 드라이버 API === ||'''함수'''||'''설명'''|| ||register_chrdev||문자 디바이스 드라이버를 등록한다|| ||unregistr_chrdev||문자 디바이스 드라이버의 등록을 해제한다|| ||cdev_init||문자 디바이스 드라이버를 위한 cdev를 할당 받는다|| ||cdev_add||문자 디바이스 드라이버를 등록한다|| ||cdev_del||문자 디바이스 드라이버의 등록을 해제한다|| ||register_chrdev_region||문자 디바이스의 디바이스 번호를 할당 받는다|| ||alloc_chrdev_region||문자 디바이스의 디바이스 번호 동적으로 할당 받는다|| ||unreigster_chrdev_region||문자 디바이스 디바이스 번호의 할당을 해제한다|| /!\ register/unregister_chrdev는 한 디바이스 번호 중 한 주 번호에 대한 모든 영역의 부 번호를 사용하는 디바이스 드라이버를 등록한다. 반면에 cdev_xxx와 xxx_chrdev_region은 부 번호를 좀 더 세밀하게 필요한 영역만 지정하여 디바이스 드라이버를 등록할 수가 있다. 다시 말해 후자가 좀 더 효율적으로 자원을 관리할 수 있다. ==== struct file_operations ==== 디바이스 드라이버의 오퍼페이션을 정의하기 위한 자료형이다. 이는 선택이 아닌 필수적으로 알아야 하는 자료형이다. 이는 에 선언되어 있다. {{{#!vim c struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); }; }}} ==== register_chrdev ==== 새로운 문자형 디바이스를 등록하는데, 이는 minor 번호가 0에서 시작해서 256개의 디바이스를 할당한다. {{{#!vim c int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); }}} ==== unregister_chrdev ==== register_chrdev를 통해 할당 받은 디바이스 자원을 해제한다. {{{#!vim c void unregister_chrdev(unsigned int major, const char *name); }}} ==== struct cdev ==== 문자 디바이스에서 사용하는 기본 자료형은 {{{struct cdev;}}}이다. 이는 에 선언되어 있다. {{{#!vim c struct cdev { struct kobject kobj; struct module *owner; struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; }}} ||'''필드'''||'''설명'''|| ||kobj||kobject 객체|| ||owner||모듈의 소유자로, 내부적으로 모듈의 ID정도로 사용된다. 일반적으로 THIS_MODULE 로 설정|| ||ops||파일 오퍼레이션을 지정한다.|| ||list||연결 리스트|| ||dev||디바이스 번호|| ||count||디바이스 개수|| /!\ THIS_MODULE 는 매크로로 에 선언되어 있다. 이는 모듈을 컴파일 시에 그 값이 정해지며, 한 시스템 내에서 서로 다른 모듈은 서로 다른 값을 갖게 된다. ==== cdev_init ==== cdev를 초기화 한다. {{{#!vim c void cdev_init(struct cdev *cdev, struct file_operations *fops) }}} cdev를 0으로 모두 초기화 하고, cdev->kobj.ktype를 ktype_cdev_default 값으로 설정하고 cdev->kobj도 kobject_init를 통해 초기화 한다. 이후 cdev->ops를 fops로 설정한다. 위 설명이야 어떻든 사용자에게 중요한 것은 cdev 내의 ops를 사용자가 넘겨준 fops로 설정하는 것이다. 사용 예는 다음과 같다: {{{#!vim c static struct file_operations xxx_fops = { .read = xxx_read, .write = xxx_write, .open = xxx_open, .release = xxx_release, .owner = THIS_MODULE, }; static struct cdev xxx_cdev = { .kobj = {.name = "xxx", }, .owner = THIS_MODULE, }; static int __init xxx_init(void) { dev_t dev = MKDEV(XXX_MAJOR, 0); if (register_chrdev_region(dev, MAX_XXX_MINORS, "xxx")) goto error; cdev_init(&xxx_cdev, &xxx_fops); ... }}} ==== cdev_add ==== 문자 디바이스에 대한 객체의 관리는 cdev_map를 통해 디바이스 번호와 매핑이 된다. 이 함수는 cdev_map에 dev 라는 디바이스 번호를 등록한다. {{{#!vim c int cdev_add(struct cdev *p, dev_t dev, unsigned count) }}} 예시: [[BR]] 다음은 {{{drivers/char/raw.c}}}의 {{{raw_init()}}} 함수의 일부 내용이다. {{{#!vim c static int __init raw_init(vodi) { int i; dev_t dev = MKDEV(RAW_MAJOR, 0); if (register_chrdev_region(dev, MAX_RAW_MINORS, "raw")) goto error; cdev_init(&raw_cdev, &raw_fops); if (cdev_add(&raw_cdev, dev, MAX_RAW_MINORS)) { unregister_chrdev_region(dev, MAX_RAW_MINORS); goto error; } ... return 0; error: ... return 1; } }}} ==== cdev_del ==== cdev_add를 통해 할당 받은 자원을 반환한다. {{{#!vim c void cdev_del(struct cdev *p) }}} 관련함수: cdev_add 예시:[[BR]] 다음은 {{{drivers/char/raw.c}}}의 {{{raw_exit}}} 함수의 일부 내용이다. {{{#!vim c static void __exit raw_exit(void) { ... cdev_del(&raw_cdev); unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), MAX_RAW_MINORS); } }}} ==== register_chrdev_region ==== cdev_init를 통해 cdev를 초기화 하기 전에 호출된다. 이는 name의 문자 디바이스를 from에 해당하는 major와 minor 번호에서 부터 시작해서 count 개수 만큼의 디바이스를 할당 받는다. 본 함수는 에 선언되어 있다. 경우에 따라서는 디바이스 번호를 동적으로 할당 받고자 할 때가 있을 것이다. 이런 경우엔 reigster_chrdev_region 대신 alloc_chrdev_region을 사용한다. {{{#!vim c int register_chrdev_region(dev_t from, unsigned count, const char *name) }}} /!\ 주의할 점은 count 값에 따라 시작 디바이스의 major 번호와 마지막 디바이스의 major 번호가 서로 다를 수가 있다. 관련함수: alloc_chrdev_region, unregister_chrdev_region ==== alloc_chrdev_region ==== 디바이스 번호를 동적으로 할당 받고자 할 때 register_chrdev_region 대신 사용한다. 해제할 때는 디바이스 번호를 알고 있으므로 unregister_chrdev_region을 사용한다. {{{#!vim c int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); }}} dev는 할당 받은 디바이스 번호를 넘겨 받을 포인터이다. name이라는 이름을 갖는 문자형 디바이스를 디바이스 번호의 minor 번호는 baseminor로 시작해서 count 개수 만큼 할당 받는다. ==== unregister_chrdev_region ==== register_crhdev_region의 대응 함수로, from에서 count 개수 만큼 할당 받은 디바이스의 자원을 반환한다. 본 함수는 에 선언되어 있다. {{{#!vim c void unregister_chrdev_region(dev_t from, unsigned count) }}} 관련함수: register_chrdev_region 예시:[[BR]] 다음은 {{{drivers/char/raw.c}}}의 {{{raw_exit}}} 함수의 일부 내용이다. {{{#!vim c static void __exit raw_exit(void) { ... cdev_del(&raw_cdev); unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), MAX_RAW_MINORS); } }}} == 장치 번호 == 장치 번호는 dev_t 형으로 u32형이다. 상위 20비트가 주번호이며 하위 12비트가 부번호이다. 내부적으로 *chrdevs[MAX_PROBE_HASH]가 선언되어 있다. MAX_PROBE_HASH는 255이다. 즉, 주번호가 인덱스가 되어야 하는데, 20비트라면 255개만으로는 주번호를 모두를 포함할 수 없다. 그래서 커널에서는 주번호%MAX_PROBE_HASH 로 나눈 값을 인덱스로 사용하여 그 인덱스를 기준으로 linked list로 연결하게 된다. === MKDEV === ma라는 major 번호와 mi라는 minor 번호를 통해 디바이스 번호를 만든다. {{{#!vim c dev_t MKDEV(ma, mi) }}} === MAJOR === 디바이스 번호에서 major 번호를 가져온다. {{{#!vim c unsigned int MAJOR(dev_t n) }}} === MINOR === 디바이스 번호에서 minor 번호를 가져온다. {{{#!vim c unsigned int MINOR(dev_t n) }}} == 프로세스 == === 스케줄링 === ==== schedule_timeout ==== 적어도 일정 시간(timeout)동안 휴면상태로 있다가 다시 실행 가능 상태가 된다. 실행 가능 상태가 된다는 말은 바로 실행된다는 것이 아니라 스케줄러에 의해 조건이 충족되면 실행된다는 의미이다. 즉, 스케줄링에 따라 일정 시간 보다 더 오랫 동안 잠정적인 휴면 상태에 있을 수도 있다. 함수 원형은 다음과 같다: {{{#!vim c signed long __sched schedule_timeout(signed long timeout) }}} timeout은 jiffies 단위 값으로 적어도 timeout 동안 휴면 상태에 있게 된다. timeout동안 휴면 상태에 있을 때 task의 상태는 다음과 같을 수 있다: ||'''상태를 나타내는 매크로'''||'''설명'''|| ||TASK_UNINTERRUPTIBLE||timeout 이후에만 깨어난다|| ||TASK_INTERRUPTIBLE||시그널 받거나 timeout이 되면 깨어난다|| timeout을 통해 깨어나게 되면 0을 반환하고 TASK_INTERRUPTIBLE 상태에서 시그널을 받아 깨어나게 되면 남은 jiffies 값을 반환한다. 관련함수: set_current_state {{{#!vim c set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(2); }}} 2 jifffies 이후에 실행 가능한 상태가 되며 그때까지 휴면 상태로 있는다. 휴면 상태에서 시그널을 받는 경우에도 깨어난다. ==== set_current_state ==== 현 task의 상태를 변경한다. 이는 헤더 파일에 정의되어 있다. {{{#!vim c void set_cureent_state(int state) }}} state 값은 다음과 같다: ||'''state'''||'''설명'''|| ||TASK_RUNNING||실행 가능한 상태|| ||TASK_INTERRUPTIBLE||시그널을 받거나 wakeup 조건을 만족하면 깨어날 수 있는 휴면 상태|| ||TASK_UNINTERRUPTIBLE||wakeup 조건을 만족할때만 깨어날 수 있는 휴면 상태|| ==== wait_event_interruptible ==== 현 프로세스를 이벤트가 발생할 때(wake_up)까지 기다리도록 한다. 단, 시그널이 발생하는 경우에는 깨어난다. 기다린다는 것은 wait_event가 실행된 시점까지만 실행하고 컨텍스트 스위칭이 일어난다는 것이다. 이후 이벤트가 발생하여 깨어나게 되면 다시 스케줄링이 되어 이후 시점부터 실행이 된다. 이 함수는 에 매크로로 정의되어 있다. {{{#!vim c wait_event_interruptible(wait_queue_head_t wq, condition) }}} condition은 C 코드 스타일의 조건문으로 condition은 바로 깨어날 조건을 의미한다. 즉 condition이 거짓일 때만 waiting 상태가 된다. 그외 유사 함수들은 다음과 같다. * wait_event * wait_event_timeout * wait_event_interrupt_timeout timeout인 경우엔 세 번째 인자로 jiffies 값인 timeout이 추가된다. 관련함수: init_waitqueue ==== wake_up_interruptible ==== wait_event_interruptible을 통해 이벤트를 기다리고 있는 프로세스를 깨어나도록 한다. 이는 에 매크로로 정의되어 있다. {{{#!vim c wake_up_interruptible(wait_queue_head_t wq) }}} 다음은 유사 함수들이다. * wake_up * wake_up_all * wake_up_interruptible * wake_up_interruptible_all * wake_up_interruptible_sync == 타임(타이머) == === BogoMIPS === 다음에 설명한 loops_per_jiffy을 기준으로 리눅스 커널에서 구한 값이다. CPU의 성능을 측정하기 위한 값으로 보기엔 어려운 값으로 단순히 loop를 돌려 한 jiffy당 얼마나 도는지를 기준으로 구한 값이다. 따라서 다양한 연산 등을 수행하는 능력을 측정하는 값이 아니다. 즉, 이를 기준으로 성능 측정을 하는 것은 바람직하지 않다. === loops_per_jiffy === 리눅스 상에서 delay 계산 등의 기준 값이 되는 것이 loops_per_jiffy 값이다. 이 변수는 init/main.c에서 전역 변수로 선언되며, init/calibrate.c 에서 그 값을 구하게 된다. 리눅스 부팅시에 보게 되는 BogoMIPS라고 하는 값이 바로 loops_per_jiffy를 기준으로 계산된 값이다. loop_per_jiffy 값을 구하는데 일정 시간의 시간이 소모되는데, 부팅 시간을 최소화 하기 위해 이 값을 이미 알고 있다면 커널 CMDLINE 값으로 넘겨줄 수도 있다. 바로 {{{lpj=jiffy당루프수}}} 라고 하여, loops_per_jiffy의 값을 직접 설정할 수도 있다. === jiffies === 커널은 필수적으로 하나의 타이머 인터럽트를 갖게 된다. 이는 스케줄링 및 타이머 등의 기준이 된다. 이때 타이머 인터럽트가 한 번 발생할 때마다 이를 tick이라 하며 하나의 tick마다 jiffies 값이 1씩 증가하게 된다. jiffies는 커널 전역 변수로 원형은 다음과 같다: {{{#!vim c volatile unsigned long jiffies; }}} === 커널 타이머 === 커널 타이머 관련 함수는 에 정의되어 있다. ==== 자료 구조 ==== {{{#!vim c struct timer_list { struct list_head entry; unsigned long expires; spinlock_t lock; unsigned long magic; void (*function)(unsigned long); unsigned long data; struct tvec_t_base_s *base; }; }}} ==== TIMER_INITIALIZER ==== {{{#!vim c TIMER_INITIALIZER(function, expires, data) }}} {{{#!vim c static struct timer_list foo_timer = TIMER_INITIALIZER(foo_timer_function, 0, 0); }}} ==== init_timer ==== {{{#!vim c void init_timer(struct timer_list *timer) }}} ==== add_timer ==== 커널 타이머를 등록하는 것으로, 내부적으로 mod_timer를 호출하여 구현된다. 그러나 mod_timer와 달리 만료 시간인 jiffies 값을 변경하는 경우엔 add_timer를 사용할 수 없다. {{{#!vim c void add_timer(struct timer_list *timer) }}} ==== del_timer ==== {{{#!vim c int del_timer(struct timer_list *timer) }}} /!\ SMP머신에서 del_timer_sync()를 사용한다. ==== mod_timer ==== {{{#!vim c int mod_timer(struct timer_list *timer, unsigned long expires) }}} === High-resolution 커널 타이머 === /!\ 커널 2.6.16부터 지원한다. {{{#!vim c struct hrtimer { struct rb_node node; ktime_t expires; enum hrtimer_state state; int (*function)(void *) void *data; struct hrtimer_base *base; }; }}} ==== hrtimer_init ==== ==== hrtimer_start ==== ==== hrtimer_cacel ==== hrtimer_try_to_cacel ==== hrtimer_restart ==== ==== hrtimer_get_remaining ==== ==== hrtimer_get_res ==== ==== hrtimer_active ==== ==== hrtimer_forward ==== ==== hrtimer_nanosleep ==== == 메모리 == 오늘 날 운영체제는 커널 공간과 사용자 공간을 독립시켜 놓고 있다. 상호 메모리 간의 보호를 위하여 이러한 체계를 사용하고 있다. 리눅스 역시 커널 공간과 사용자 공간이 분리되어 있다. 서로 다른 공간에 속한 메모리에 접근하기 위해서는 정해진 규약이나 API를 통해 접근해야 한다. 가장 간단한 방법은 copy_from/to_user나 get/put_user 등을 이용하여 상호 간의 메모리 공간의 데이터를 복사해서 사용하는 것이다. 또는 mmap과 같은 것을 이용하여 커널 내 공간을 사용자 레벨에서 접근할 수 있도록 매핑하는 방법이 있다. 반대로 get_user_pages를 이용하여 사용자 공간의 페이지를 커널 내에서 접근할 수 있도록 매핑하는 방법도 있다. === 사용자/커널 공간 === 메모리 영역은 사용자 영역과 커널 영역으로 나뉜다. 일반적으로 사용자 영역의 경우 page out(swap out)된 경우가 있을 수 있다. 이런 메모리 공간을 커널 영역에서 직접 접근하는 경우 page fault 등이 생길 수 있는데, 커널 공간의 일부 처리 중에는 이러한 것이 문제가 될 수 있다. 반대로 커널 영역의 메모리는 사용자 영역의 프로세스에서 직접 접근할 수 없다. 따라서 서로 다른 영역을 메모리를 접근하기 위해서는 이에 필요한 적절한 API를 이용해야 한다. ==== copy_from_user ==== 사용자 영역의 메모리에 있는 데이터를 커널 영역의 메모리 영역으로 복사할 때 사용하는 함수이다. 이 함수는 아키텍처에 의존적인 함수로 헤더 파일에 선언되어 있다. {{{#!vim c unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) }}} ||'''매개변수'''||'''설명'''|| ||to||커널 영역의 메모리 주소|| ||from||사용자 영역의 메모리 주소|| ||n||바이트 단위의 데이터 크기|| 성공하면 0을 반환하고 실패하면 0이 아닌 값을 반환한다. 관련함수: copy_to_user /!\ 함수 내부적으로 access_ok를 통해 사용자의 메모리 영역을 검사하므로 별도의 access_ok를 호출할 필요는 없다. ==== copy_to_user ==== {{{#!vim c unsigend long copy_to_user(void __user *to, const void *from, unsigned long n) }}} ==== get_user ==== 사용자 영역의 데이터를 커널 영역으로 복사할 때 사용한다. 이는 아키텍처 의존적인 함수로 에 선언되어 있다. {{{#!vim c int get_user(x, void *from) }}} x는 char, short, int 형으로 넘겨지는 데이터 형에 따라 복사 할 데이터의 길이(바이트 단위)가 결정된다. ==== put_user ==== 커널 영역의 데이터를 사용자 영역으로 복사한다. 이는 아키텍처 의존적인 함수로 에 선언되어 있다. {{{#!vim c int put_user(x, void *to) }}} x는 char, short, int 형으로 넘겨지는 데이터 형에 따라 복사 할 데이터의 길이(바이트 단위)가 결정된다. ==== verify_area ==== 다음절에서 소개하는 access_ok의 wrapper 함수이다. {{{#!vim c int verify_area(int type, const void __user * addr, unsigned long size) }}} 메모리가 유효하면 0을 반환하고 그렇지 않으면 -EFAULT 값을 반환한다. /!\ acess_ok함수와 동일한 것이며 사용자는 verify_area함수를 사용하길 권한다. ==== access_ok ==== 사용자 영역의 메모리가 유효한지 검사한다. 에 선언되어 있다. {{{#!vim c int access_ok(int type, void *addr, unsigned long size); }}} ||'''인자'''||'''설명'''|| ||type||어떤 유효 검사를 할지를 결정한다. 다음 표를 참조|| ||addr||유효 검사를 시작할 주소|| ||size||유효 검사를 할 영역의 바이트 단위 크기|| type 매개 변수는 매크로로 선언되어 있는데, 다음과 같다: ||'''type'''||'''설명'''|| ||VERIFY_READ||읽기에 대한 유효 검사|| ||VERIFY_WRIT||쓰기에 대한 유효 검사|| /!\ 다른 함수와 달리 유효하다면 0이 아닌 값을 반환하고 유효하지 않다면 0을 반환한다. /!\ 동일한 기능의 함수로 access_ok의 wrapper함수가 있는데 verify_area함수가 있다. 사용자는 이것을 사용하길 권한다. 관련함수: verify_area() copy_from_user/copy_to_user 또는 get_user/put_user 등의 함수는 access_ok를 사용할 필요가 없다. __get_user 등과 같이 더 저 수준의 함수를 직접 호출하여 사용할 때만 사용하게 된다. 다음은 access_ok를 사용한 예이다. {{{#!vim c static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) { if (access_ok(VERIFY_READ, from, n)) n = __arch_copy_from_user(to, from, n); else memzero(to, n); return n; } }}} === 메모리 매핑 === 물리 메모리와 가상 메모리를 매핑하는 함수에 대해 설명한다. ==== remap_page_range ==== 본 함수는 물리 메모리 영역을 가상 메모리 영역으로 매핑시켜 준다. /!\ 커널 2.6.10 이후부터는 더이상 사용되지 않는다. 함수 원형은 다음과 같다. {{{#!vim c int remap_page_range(struct vm_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_t prot); }}} ||'''인자'''||'''설명'''|| ||vma||프로세스에서 넘겨준 가상 메모리에 대한 디스크립터|| ||from||매핑할 가상 메모리의 시작 번지. 보통은 vma->vm_start를 넘긴다.|| ||to||매핑할 물리 메모리의 시작 번지|| ||size||매핑할 메모리의 크기|| ||prot||매핑된 메모리에 대한 보호 권한. 보통은 vma->vm_page_prot를 넘긴다.|| 일반적으로 위 함수를 호출하기 전에 vm->vm_flags에 매핑할 메모리가 어떤 특성을 갖는지를 선언한다. 이들 플래그에 대한 자세한 것은 를 참고하라. /!\ 앞서 커널 2.6.10 이후에는 더 이상 사용되지 않는다고 했는데, 그 이유는 물리 메모리 주소를 넘겨주는 to 인자가 32비트이기 때문이다. 4GB이상의 물리 메모리 영역을 매핑할 수 없는 한계가 있다. 그래서 remap_pfn_range()를 사용하도록 하고 있다. ==== remap_pfn_range ==== 본 함수는 알고 있는 물리 메모리를 매핑할 가상 메모리를 알고 있을 때, 물리 메모리 영역을 가상 메모리 영역으로 매핑시켜 준다. 따라서 remap_pfn_range의 인자 중 가상 메모리 기술자(vam)와 가상 메모리의 시작 주소(addr) 그리고 물리 조수의 페이지 프레임 번호(pfn)을 사전에 알아야 한다. /!\ 커널 2.6.10 이후부터 remap_page_range()함수를 remap_pfn_range()함수로 대체되었다. 함수 원형은 다음과 같다. {{{#!vim c int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot) }}} ||'''인자'''||'''설명'''|| ||vma||프로세스에서 넘겨준 가상 메모리에 대한 디스크립터|| ||addr||매핑할 가상 메모리의 시작 번지. 보통은 vma->vm_start를 넘긴다.|| ||pfn||매핑할 물리 메모리가 속한 페이지 프레임 번호|| ||size||매핑할 메모리의 크기|| ||prot||매핑된 메모리에 대한 보호 권한. 보통은 vma->vm_page_prot를 넘긴다.|| 일반적으로 물리 메모리 주소를 사용하게 될텐데, 물리 메모리 주소에서 pfn를 구하는 방법은 PAGE_SHIFT 만큼 쉬프트를 시키면 된다. 즉 {{{물리주소 >> PAGE_SHIFT}}}가 pfn이다. 경우에 따라서는 이미 있는 가상 메모리로 매핑되어 있는 물리 메모리 주소를 사용할 필요가 있을 수도 있다. 이런 경우엔 virt_to_phys(x) >> PAGE_SHIFT를 이용하는 방법이 있다. 일반적으로 위 함수를 호출하기 전에 vm->vm_flags에 매핑할 메모리가 어떤 특성을 갖는지를 선언한다. 이들 플래그에 대한 자세한 것은 를 참고하라. pgprot_t형의 prot 값은 변경할 수 있는데, 캐시와 쓰기 버퍼를 사용하지 않거나 캐시만 사용하지 않도록 할 수 있다. 이런 경우를 위해 두개의 매크로를 정의하고 있다(에 선언). * pgprot_noncached(prot) * pgprot_writecombine(prot) 주소 매핑이 성공이면 0을 리턴하고 그렇지 한다면 0이 아닌 수를 리턴한다. 따라서 반드시 리턴값을 확인해야만 한다. 다음은 커널 2.6.15의 drivers/usb/media/pwc/pwc-if.c에 있는 코드의 일부이다. {{{#!vim c static int pwc_video_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *vdev = file->private_data; struct pwc_device *pdev; unsigned long start = vma->vm_start; unsigned long size = vma->vm_end - vma->vm_start; unsigned long page, pos; pdev = fdev->priv; vma->vm_flags |= VM_IO; pos = (unsigned long)pdev->image_data; while (size > 0) { page = vmalloc_to_pfn((void *)pos); if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) return -EAGAIN; start += PAGE_SIZE; pos += PAGE_SIZE; if (size > PAGE_SIZE) size -= PAGE_SIZE; else size = 0; } return 0; } }}} 이는 vma를 통해 넘겨 받은 가상 메모리와 vmalloc를 통해 할당 받은 image_data 메모리 주소를 remap_pfn_range를 통해 매핑하고 있다. 여기서 vmalloc_to_pfn를 사용하는데, 이는 vmalloc를 통해 할당 받은 메모리를 페이지 프레임 번호(pfn)으로 변환한 갑을 리턴한다. ==== get_user_pages ==== 사용자 영역의 메모리 주소를 커널 단에 직접 사용하기 위하여 매핑시킨다. 본 함수는 에 선언되어 있다. {{{#!vim c int get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, int len, int write, int force, struct page **pages, struct vm_area_struct **vmas) }}} ||'''인자'''||'''설명'''|| ||tsk||호출한 프로세스의 태스크 기술자. 보통은 current가 된다|| ||mm||호출한 프로세스의 메모리 기술자. 보통은 current->mm이 된다|| ||start||사용자 공간의 버퍼 메모리 주소|| ||len||할당 받을 페이지 수|| ||write||쓰기면 1, 읽기면 0|| ||force|||| ||pages||할당 받은 페이지 테이블|| ||vmas|||| 다음은 한 페이지 정도의 크기를 할당 받아 사용하는 간단한 예이다. {{{#!vim c struct mm_struct *mm = current->mm; struct page *page; int ret; int offset; void *maddr; down_read(&mm->mmap_sem); ret = get_user_pages(current, mm, start, 0, 1, &page, NULL); up_read(&mm->mmap_sem); if (ret <= 0) return -1; offset = start & (PAGE_SIZE - 1); maddr = kmap(page); /* user's code */ kunmap(page); page_cache_release(page); }}} page를 할당 받으면 kmap을 통해 커널 공간의 가상 메모리로 매핑하여 사용해야 한다. '''실제 데이터가 있는 공간은 maddr+offset 번지부터 있음'''을 주의해야 한다. === IO 메모리 매핑 === IO 메모리는 일반적인 메모리와 달리 캐시가 되면 안 된다. 즉 어떤 값을 읽거나 쓸 때 그 값이 IO에 바로 반영이 되어야지, 캐시와 같은 곳에 반영이 된다면 IO의 동작을 예상할 수 없는 결과를 낳게 된다. 또한 write buffer 기능 역시 사용하지 않는다. 이러한 특수한 경우를 위하여 IO 메모리와 관련된 함수를 별도로 제공하고 있다. IO 메모리와 관련된 함수는 에 선언되어 있다. ==== request_mem_region / release_mem_region ==== IO 메모리를 사용하기 위하여 실제로 메모리 영역을 할당 받기 전에 그 영역이 사용 가능한 상태(이미 할당되어 있다면 사용할 수 없다)인지를 확인하거나 요청할 수 있다. 만약, 사용중이 아니라면 등록하여 사용할 수 있다. 사용이 모두 끝나면 등록을 해제하여 IO를 필요로 하는 것들이 사용할 수 있도록 해야 한다. IO 메모리에 관련된 인터페이스는 에 정의되어 있다. IO를 위한 메모리를 할당은 다음과 같이 한다. {{{#!vim c char *name = "MyIO"; if (!request_mem_region(phys, size, name)) return -EBUSY; virt = ioremap(phys, size); }}} * phys : 물리 메모리 시작 번지 * size : IO 메모리로 할당할 메모리의 크기 할당 받은 IO 메모리를 해제하는 경우는 다음과 같다. {{{#!vim c release_mem_region(phys, size); iounmap((void *)virt); }}} * virt : 해제할 가상 메모리 시작 번지 ==== ioremap ==== IO를 위한 물리 메모리 주소와 그 크기를 넘겨주면 커널 내에서 사용할 수 있는 가상 메모리를 구한다. {{{#!vim c void __iomem *ioremap(unsigned long phys, unsigned long size) }}} ||'''인자'''||'''설명'''|| ||phys||IO 메모리의 시작 주소(물리 주소)|| ||size||IO 메모리의 크기|| 매핑 된 주소는 사용이 끝나면 매핑을 해제해야만 한다. 이는 iounmap을 이용한다. 관련 함수: iounmap ==== iounmap ==== ioremap을 통해 매핑한 가상 메모리 주소는 그 사용이 끝나면 매핑을 해제해야 한다. 이 때 사용하는 것이 iounmap이다. {{{#!vim c void iounmap(volatile void __iomem *addr) }}} 관련 함수: ioremap ==== io_remap_pfn_range ==== 에 선언되어 있다. io_remap_pfn_range는 remap_pfn_range와 동일하다. 따라서 자세한 설명은 remap_pfn_range을 참고하자. {{{#!vim c #define io_remap_pfn_range(vma, from, pfn, size, prot) \ rempa_pfn_range(vma, from, pfn, size, prot) }}} ==== readb/w/l ==== {{{#!vim c unsigned char readb(const volaltile void __iomem *addr) }}} {{{#!vim c unsigned short readw(const volaltile void __iomem *addr) }}} {{{#!vim c unsigned int readl(const volaltile void __iomem *addr) }}} ==== writeb/w/l ==== {{{#!vim c void writeb(unsigned char b, const volaltile void __iomem *addr) }}} {{{#!vim c void writew(unsigned short b, const volaltile void __iomem *addr) }}} {{{#!vim c void writel(unsigned int b, const volaltile void __iomem *addr) }}} === 커널 메모리 === ==== kmalloc ==== {{{#!vim c void *kmalloc(size_t size, gfp_t flags); }}} * GFP_ATOMIC * GFP_KERNEL * __GFP_DMA {{{#!vim c void kfree(const void *objp); }}} ==== kzalloc ==== {{{#!vim c void *kzalloc(size_t size, gfp_t flags); }}} * GFP_ATOMIC * GFP_KERNEL * __GFP_DMA {{{#!vim c void kfree(const void *objp); }}} ==== vmalloc ==== {{{#!vim c void *vmalloc(unsigned long size); }}} {{{#!vim c void vfree(void *addr); }}} ==== kmem_cache_create ==== {{{#!vim c kmem_cache_t *kmem_cache_create(const char *name, size_t size, size_t offset, unsigned long flags, void (*constructor)(void *, kmem_cache_t *, unsigned long flags), void (*destructor)(void *, kmem_cache_t *, unsigned long flags)); }}} flags: * SLAB_NO_REAP * SLAB_HWCACHE_ALIGN * SLAB_CACHE_DMA 관련함수 : kmem_cache_alloc, kmem_cache_free, kmem_cache_destroy ==== kmem_cache_alloc ==== cache에서 한 객체를 위한 메모리를 할당 받는다. {{{#!vim c void *kmem_cache_alloc(kmem_alloc_t *cache, gfp_t flags); }}} '''cache''' kmem_cache_create를 통해 리턴 받은 값. '''flags''' SLAB_NOFS - GFP_NOFS와 동일[[BR]] SLAB_NOIO - GFP_NOIO와 동일[[BR]] SLAB_ATOMIC - GFP_ATOMIC와 동일[[BR]] SLAB_USER - GFP_USER와 동일[[BR]] SLAB_KERNEL - GFP_KERNEL와 동일[[BR]] SLAB_DMA - GFP_DMA와 동일[[BR]] 관련함수: kmem_cache_create, kmem_cache_free, kmem_cache_destory ==== kmem_cache_free ==== {{{#!vim c void kmem_cache_free(kmem_cache_t *cache, const void *obj); }}} 관련함수 : kmem_cache_create, kmem_cache_alloc, kmem_cache_destroy ==== kmem_cache_destroy ==== {{{#!vim c int kmem_cache_destroy(kmem_cache_t *cache); }}} 관련함수 : kmem_cache_create, kmem_cache_alloc, kmeme_cache_free ==== get_zeroed_page ==== {{{#!vim c get_zeroed_page(unsigned int flags); }}} 관련함수: free_page ==== __get_free_page ==== {{{#!vim c __get_free_page(unsigned int flags); }}} 관련함수: free_page ==== __get_free_pages ==== {{{#!vim c __get_free_pages(unsigned int flags, unsigned int order); }}} {{{#!vim c void free_pages(unsigend long addr, unsigned long order); }}} 관련함수: free_pages === 유용한 매크로 === * page_to_pfn * prn_to_page * prn_valid * virt_to_page * virt_addr_valid * virt_to_phys * vmalloc_to_pfn == MMAP == 크게 보면 메모리에 속하는 부분이긴 하지만, DMA와도 연관이 있어 MMAP이라는 절로 따로 두었다. 일부 내용은 메모리나 DMA에서 다루었던 내용과 중첩될 수도 있다. MMAP을 구현하는 방법으로는 다음과 같은 방법이 있다. * dma_mmap_writecombine 을 사용하는 것 * io_remap_pfn_range(또는 remap_pfn_range) 을 사용하는 것 ==== dma_mmap_writecombine ==== /!\ (주의) arm 아키텍처에서만 제한적으로 지원된다. 에 선언되어 있다. {{{#!vim c int dma_mmap_writecombine(struct device *dev, struct vm_area_struct *vma, void *cpu_addr, dma_addr_t handle, size_t size); }}} '''dev''' 디바이스 포인터 '''vma''' user level의 mmap에서 넘겨 받은 vma에 대한 정보 '''cpu_addr''' 커널에서 사용 가능한 가상 메모리 영역. 보통 dma_alloc_writecombine을 통해 할당 받은 메모리의 가상 주소 '''handle''' 커널에서 사용 가능한 가상 메모리 영역에 매핑된 물리 메모리. 보통 dma_alloc_writecombine을 통해 할당 받은 메모리의 물리 주소 '''size''' MMAP으로 매핑할 메모리 크기 관련함수: dma_free_writecombine 다음은 fb 드라이버에서 사용한 mmap 구현의 한 예이다(drivers/video/pxafb.c). {{{#!vim c static int __init pxafb_map_video_memory(struct pxafb_info *fbi) { ... fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE); fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, &fbi->map_dma, GFP_KERNEL); if (fbi->map_cpu) { memset(fbi->map_cpu, 0, fbi->map_size); fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE; fbi->screen_dma = fbi->map_dma + PAGE_SIZE; fbi->fb.fix.smem_start = fbi->screen_dma; ... } ... } static int pxa_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct pxafb_info *fbi = (struct pxafb_info *)info; unsigned long off = vma->vm_pgoff << PAGE_SHIFT; if (off < info->fix.smem_len) { vma->vm_pgoff += 1; return dma_mmap_writecombine(fbi->dev, vma, fbi->map_cpu, fbi->map_dma, fbi->map_size); } return -EINVAL; } }}} ==== dma_free_writecombine ==== /!\ (주의) arm 아키텍처에서만 제한적으로 지원된다. 에 선언되어 있다. {{{#!vim c dma_free_writecombine(struct device *dev, size_t size, void * cpu_addr, dma_addr_t handle); }}} 관련함수: dma_mmap_writecombine ==== io_remap_pfn_range ==== 이 함수에 대한 설명은 IO 메모리 절을 참고하라. 여기서는 io_remap_pfn_range을 이용해서 mmap을 구현하는 방법에 대해 소개한다. 실제 예를 보자(drivers/video/acornfb.c). {{{#!vim c static int acornfb_mmap(struct fb_info *info, struct vm_area_struct *vma) { unsigned long off, start; u32 len; off = vma->vm_pgoff << PAGE_SHIFT; start = info->fix.smem_start; len = PAGE_ALIGN(start & ~PAGE_MASK) + info->fix.smem_len; start &= PAGE_MASK; if ((vma->vm_end - vma->vm_start + off) > len) return -EINVAL; off += start; vma->vm_pgoff = off >> PAGE_SHIFT; vma->vm_flags |= VM_IO; vma->vm_page_prot = pgrot_writecombine(vma->vm_page_prot); if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0; } }}} == DMA == === 메모리 할당 === ==== dma_alloc_coherent ==== 캐시는 사용하지 않지만, write 버퍼는 사용한다. {{{#!vim c void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, int gfp) }}} DMA에서 사용할 메모리를 할당할 때 사용하는데, 가상 주소 값을 반환 하며, 물리 주소 값은 handle를 통해 반환한다. 메모리 할당에 실패하면 NULL값을 반환한다. 관련함수: dma_alloc_writecombine ==== dma_alloc_writecombine ==== 캐시 및 write 버퍼 기능을 모두 사용하지 않는다. {{{#!vim c void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, int gfp) }}} 반환값은 가상 주소이며, 물리 주소는 handle에 저장된다. 관련함수: dma_alloc_coherent ==== Streaming DMA ==== dma 매핑과 관련된 헤더는 이다. 스트리밍 매핑에서 자료가 어느 방향으로 움직이는지를 명시해야 한다. 이 때 사용하는 enum 값은 다음과 같다. {{{#!vim c enum dma_data_direction { DMA_BIDIRECTIONAL = 0, DMA_TO_DEVICE = 1, DMA_FROM_DEVICE = 2, DMA_NONE = 3, }; }}} ==== dma_map_single ==== {{{#!vim c dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction); }}} ==== dma_unmap_single ==== {{{#!vim c void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction); }}} ==== dma_sync_single_for_cpu ==== ==== dma_sync_single_for_device ==== === DMA POOL === ==== dma_pool_create ==== ==== dma_pool_destroy ==== ==== dma_pool_alloc ==== ==== dma_pool_free ====