2016. 8. 11. 12:08 devel/개념
mmap 구현 - 작성중
mmap 구현 방식 2가지
ldd 많이 참조함, 옅은 지식으로 쓴 내용이라 틀릴 수 있음
mmap의 역할?
kernel 공간 혹은 하드웨어 장치에 있는 메모리를 복사하는 과정 없이 user 공간에게 제공
rw가 엄청나게 발생하는 경우에 꽤나 의미있는 성능향상을 보여줄 수 있다
c 어플리케이션에서 mmap 함수를 호출하면 device driver의 mmap 함수가 호출된다
그 함수를 구현하는게 목표
어플리케이션 영역
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
자세한 설명은 man 페이지 참조
flag는 계속 늘어나는데 MAP_HUGETLB 같이 huge page를 쓸 때 필요한 옵션도 있다
그냥 일반적인 flag로는 MAP_SHARED(다른 프로세스랑 같이 공유) 가 있겠다
prot는 page의 권한에 관한 내용이다
페이지가 읽힐 예정인지 쓰거나 실행될련지 등등
mmap 구현
요약 : 아래의 함수들을 잘 조합해서 사용하면 된다
io_remap_pfn_range()
remap_pfn_range()
아니면 fault 를 처리하는 방식으로 구현해도 ok
vm_insert_page()
vm_insert_pfn()
fault 처리에 주로 사용된다
원형
include/linux/fs.h
struct file_operation {
...
int (*mmap) (struct file *, struct vm_area_struct *);
}
remap_pfn_range 사용
remap_pfn_range - remap kernel memory to userspace
system RAM을 맵핑하고 싶을 때 사용
참고 : io_remap_pfn_range은 장치의 I/O memory 즉 phys_addr_t로 표현되는 놈을 매핑하고 싶을 때 사용
nommu가 아닌이상 동일
내가 파악한 바로는(틀릴 수 있지만 ㅠㅜ)
- 유저에서 mmap을 호출하면 (사용할 수 있는 커널 메모리 쫌만 주세요. 대신 제가 알아볼 수 있는 주소로요)
- 위의 요청 정보가 vma 형태로 driver로 들어오고(메모리 정보)
- 인제 개발자는 가지고 있던(미리 할당해두었던) 메로리를 vma랑 붙여주는데
- 그 때 사용하는 도구가 remap_pfn_range 인가보다
이 함수는 userspace의 addr ~ addr+size 범위의 virtual address를 위한 page table을 만든다
이 함수는 pfn<<PAGE_SHIFT ~ (pfn<<PAGE_SHIFT) + size 까지 해당하는 물리 메모리 영역에 영향을 준다
include/linux/pfn.h
#define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHIFT)
#define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT))
int remap_pfn_range(struct vm_area_struct *vma,
unsigned long addr,
unsigned long pfn,
unsigned long size,
pgprot_t prot)
vma : 매핑될 virtual memory area - 변태가 아닌 이상 mmap 호출될 때 vma 인자가 같이 딸려오므로 그대로 쓰면 된다. 하지만 변태라면?!
addr : target user address to start at
pfn : page frame number of kernel memory
보통 physical address >> PAGE_SHIFT 한 값이다
이 physical address에 addr(target user address, virtual address)가 매핑되게 된다
size : 바이트 단위의 매핑될 영역 크기. 보통 vma->vm_end - vma->vm_start
prot : page protection flag, 보통 vma->vm_page_prot 값을 사용한다
이 함수를 호출할 때 흔히 다음과 같이 한다(아래 참조)
예시 : drivers/uio/uio.c
static const struct file_operations uio_fops = {
...
.mmap = uio_mmap,
}
static int uio_map(struct file *filep, struct vm_area_struct *vma)
{
...
return uio_mmap_physical(vma);
}
static int uio_mmap_physical(struct vm_area_struct *vma)
{
...
return remap_pfn_range(vma,
vma->vm_start,
mem->addr >> PAGE_SHIFT, //미리 할당해두었던 메모리
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
조금 형태가 다르긴 한데 이런 것도 있다
drivers/vfio/pci/vfio_pci.c
static int vfio_pci_mmap(void *device_data, struct vm_area_struct *vma)
{
...
pgoff = vma->vm_pgoff &
((1U << (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT)) - 1); //???
u64 phys_len = pci_resource_len(pdev, index);
req_start = pgoff << PAGE_SHIFT;
req_len = vma->vm_end - vma->vm_start;
if(phys_len < PAGE_SIZE || req_start + req_len > phys_len) //크기 검사
return -EINVAL;
vma->vm_pgoff = (pci_resource_start(pdev, index) >> PAGE_SHIFT) + pgoff;
return remap_pfn_range(vma, vma->start, vma->vm_pgoff,
req_len, vma->vm_page_prot);
//어차피 remap_pfn_range를 호출하면 vma->vm_pgoff = pfn 이 된다
}
아니면 이런 애들도 있다
sound/core/pcm_native.c
int snd_pcm_lib_default_mmap(...)
{
area->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
#ifdef CONFIG_GENERIC_ALLOCATOR
if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_IRAM) {
area->vm_page_prot = pgprot_writecombine(area->vm_page_prot);
return remap_pfn_range(area, area->vm_start,
substream->dma_buffer.addr >> PAGE_SHIFT,
area->vm_end - area->vm_start, area->vm_page_prot);
}
#endif /* CONFIG_GENERIC_ALLOCATOR */
#ifndef CONFIG_X86 /* for avoiding warnings arch/x86/mm/pat.c */
if (!substream->ops->page &&
substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV)
return dma_mmap_coherent(substream->dma_buffer.dev.dev,
area,
substream->runtime->dma_area,
substream->runtime->dma_addr,
area->vm_end - area->vm_start);
#endif /* CONFIG_X86 */
/* mmap with fault handler */
area->vm_ops = &snd_pcm_vm_ops_data_fault;
return 0;
}
기타 다른 예시
drivers/infiniband/hw/nes/nes_verbs.c
static int nes_mmap(struct ...)
{
...
remap_pfn_range( ...
virt_to_phys >> PAGE_SHIFT ...)
...
}
.pgprot
잘 몰라서 생략
대략 cache 쓸껀지 말껀지 관한 내용인데
pgprot_noncached, pgprot_writecombine, pgprot_writethrough
등이 있다
기타
drivers/media/platform/omap/omap_vout.c
OMAP 24xx camera controller 코드
처음 매몰이 할당 코드
virt_addr = omap_vout_alloc_buffer(vout->buffer_size, &phy_addr);
vout->buf_virt_addr[i] = virt_addr;
vout->buf_phy_addr[i] = phy_addr;
unsigned long omap_vout_alloc_buffer(u32 buf_size, u32 *phys_addr)
{
u32 order, size;
unsigned long virt_addr, addr;
size = PAGE_ALIGN(buf_size); //참고로 가로px * 세로px * bpp(bits per pixel)
order = get_order(size);
virt_addr = __get_free_pages(GFP_KERNEL, order);
addr = virt_addr;
*phys_addr = (u32) virt_to_phys((void *) virt_addr);
return virt_addr;
}
mmap 코드
vm_operations_struct omap_vout_vm_ops
.open = vout->mmap_count++; 역할 수행(mmap )
.close = vout->mmap_count--; 역할 수행
mmap 호출한다고 해서 mmap open까지 같이 호출되지는 않는다
놀랍게도 r/w 둘 다 open을 호출하지 않는다
open은 process가 fork할 때 불려진다
그리고 vma에 대한 새로운 reference를 생성한다
static int omap_vout_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long start = vma->vm_start;
unsigned long size = vma->vm_end - vm->vm_start;
...
void* pos = (void *)vout->buf_virt_addr[i]; //kernel logical address(virtual address)
vma->vm_pgoff = virt_to_phys((void *) pos) >> PAGE_SHIFT; //physical
vma->vm_ops = &omap_vout_vm_ops;
...
while(size > 0) {
unsigned long pfn;
pfn = virt_to_phys((void *) pos) >> PAGE_SHIFT; //물리 주소 획득
if(remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED))
return -EAGAIN;
start += PAGE_SIZE;
pos += PAGE_SIZE;
size -= PAGE_SIZE;
}
vout->mmap_count++;
}
remap_pfn_range에 size를 한 번에 넣는게 아니라 PAGE_SIZE로 나눠 하는 이유?
pixel size가 크다고 해도 어차피 kmalloc 한계인 MAX_ORDER(2^ 10 ~11, 1024) * PAGE_SIZE(4096B) = 4MB or 8MB 이내 범위로 메모리가 할당되어 있다
왜 이짓을 하냐면 메모리가 cma같은게 아닌이상 크게 할당이 안되고 PAGE_SIZE 크기로 할당해서 일듯?
vma 유저 메몰희랑 pfn 물리 매몰의랑
추가 참고 : http://www.cs.fsu.edu/~baker/devices/notes/ch15.html#(34)
그 외 fault 직접 관리
vm_opeartions_struct 의 fault 함수를 직접 구현해야 함
결론적으로 vmf->page = page 를 해주어 메모리를 제공하면 끝
제공해줄 page는 어디서 얻냐고?
여러가지 구현 방법이 있는데 get_zeroed_page 같은 함수로 미리 얻어놓거나
필요할 때마다 획득하거나 등등
예시 : sound/usb/usx2y/usX2Yhwdep.c, sound/core/memalloc.c
mmap이 호출되면 __get_free_pages 로 shared memory 호출하고
fault가 호출되면 아래의 함수가 호출된다
static int snd_us428ctls_vm_fault(struct vm_area_struct *area,
struct vm_fault *vmf)
{
...
offset = vmf->pgoff << PAGE_SHIFT; //physical to kernel logical
vaddr = (char*)((struct usX2Ydev *)area->vm_private_data)->us428ctls_sharedmem + offset;
//미리 얻어놨던 메모리 조금씩 제공
page = virt_to_page(vaddr);
vmf->page = page;
get_page(page); // 나 이 페이지 쓴다!
}
혹은 다른 예시 - dma 같은게 필요 없는 경우
drivers/uio/uio.c
static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
page = vmalloc_to_page(addr);
get_page(page);
vmf->page = page;
}
drivers/staging/android/ion/ion.c
static int ion_vm_fault(staruct vm_area_struct *vma, struct vm_fault *vmf)
{
...
vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn);
...
}
그 외 dma common mmap
include/linux/dma-mapping.h
struct dma_map_ops {
...
int (*mmap)(struct device *, struct vm_area_struct *,
void *, dma_addr_t, size_t, struct dma_attrs *attrs);
}
drivers/base/dma-mapping.c 참고
int dma_common_mmap(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size)
{
int ret = -ENXIO;
#if defined(CONFIG_MMU) && !defined(CONFIG_ARCH_NO_COHERENT_DMA_MMAP)
unsigned long user_count = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
unsigned long count = PAGE_ALIGN(size) >> PAGE_SHIFT;
unsigned long pfn = page_to_pfn(virt_to_page(cpu_addr));
unsigned long off = vma->vm_pgoff;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
include/linux/mm.h
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
if (off < count && user_count <= (count - off)) {
ret = remap_pfn_range(vma, vma->vm_start,
pfn + off,
user_count << PAGE_SHIFT,
vma->vm_page_prot);
}
#endif /* CONFIG_MMU && !CONFIG_ARCH_NO_COHERENT_DMA_MMAP */
return ret;
}
'devel > 개념' 카테고리의 다른 글
gpio pull down switch 연결 (0) | 2016.11.16 |
---|---|
도요타 캠리 급발진 버그 분석 보고서 (0) | 2016.11.10 |
stm32f4 절전모드 (0) | 2016.08.10 |
__GFP_COMP - compound page (0) | 2016.07.26 |
ld linker script 예시 (0) | 2016.07.25 |