Linux ドライバーは「カーネル」空間で実行されます。一般に、ドライバーは kmalloc() を呼び出してデータ構造にメモリを割り当て、vmalloc() を呼び出してアクティブ スワップ領域にデータ構造を割り当て、一部の I/O ドライバーにバッファを割り当て、またはモジュールにスペースを割り当てます。kmalloc と vmalloc はカーネル メモリを割り当てます。
#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。
Linux ドライバーは「カーネル」空間で実行されます。
一般的なマイコンプログラムは、アプリケーションとドライバが混在することが多く、ある程度の能力のあるマイコンプログラマであれば、アプリケーションとドライバの階層化を実現できます。 Linux システムでは、アプリケーションとドライバーを階層化することが強制されています。
マイクロコントローラー プログラムでは、アプリケーションは基礎となるレジスタを直接操作できます。ただし、Linux システムでは、そのような動作は禁止されています。例: Linux アプリケーションの作成者は、意図的にアプリケーション内の電源管理ドライバーを呼び出し、システムをシャットダウンします。これは利益を得る価値があるのではないでしょうか?
特定の Linux アプリケーションは、図に示すようにドライバーを呼び出します。
アプリケーションはユーザー空間で実行され、ドライバーはカーネル空間で実行されます。 。ユーザー空間内のアプリケーションがカーネルを操作したい場合、「システムコール」メソッドを使用してユーザー空間からカーネル空間に入り、基礎となる層を操作する必要があります。
カーネルもプログラムであり、独自の仮想メモリ空間を持つ必要がありますが、ユーザー プログラムにサービスを提供するプログラムとして、カーネルは空間にはそれぞれの特徴があります。
カーネル空間とユーザー空間の関係
32 ビット システムでは、プログラムの最大仮想空間は 4GB です。直接的なアプローチは、カーネルをプログラムとみなして、他のプログラムと同じ 4GB のスペースを持つようにすることです。ただし、このアプローチでは、システムがユーザー プログラムのページ テーブルとカーネル ページ テーブルを継続的に切り替えることになるため、コンピュータの効率に影響します。この問題を解決する最善の方法は、4GB スペースを 2 つの部分に分割することです: 1 つの部分はユーザー スペースで、もう 1 つの部分はカーネル スペースです。これにより、カーネル スペースが固定され、変更されず、プログラムが切り替わったときに確実に変更できます。 、プログラムのページテーブルのみ。このアプローチの唯一の欠点は、カーネル空間とユーザー空間の両方が小さくなることです。
例: i386 などの 32 ビット ハードウェア プラットフォームでは、Linux はファイル page.h に定数 PAGE_OFFSET を定義します。
#ifdef CONFIG_MMU #define __PAGE_OFFSET (0xC0000000) //0xC0000000为3GB #else #define __PAGE_OFFSET (0x00000000) #endif #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
Linux は PAGE_OFFSET を境界 4GB の仮想メモリ空間は 2 つの部分に分割されます: アドレス 0 ~ 3G-1 の低アドレス空間は 3GB のサイズを持つユーザー空間であり、アドレス 3GB ~ 4GB-1 の高アドレス空間はカーネル空間であり、 1GBのサイズ。
システム内で複数のプログラムが実行されている場合、複数のユーザー空間とカーネル空間の関係は次のように表すことができます。図に示すように、プログラム 1、2...n はカーネル空間を共有します。もちろん、ここでの共有とはタイムシェアリングを指します。シングルコア プロセッサ システムでは、常に 1 つのプログラムしか実行できないからです。
カーネル空間の全体的なレイアウトLinuxの開発プロセスでは、ハードウェア機器の更新と技術レベルの向上が伴います。 、そのカーネル空間 レイアウトの開発は、継続的なパッチ適用プロセスでもあります。この結果、カーネル空間はいくつかの異なる領域に分割され、異なる領域には異なるマッピング方法が適用されます。 通常、Linux カーネル空間には、DMA 領域 (ZONE_DMA)、通常領域 (ZONE_NORMAL)、およびハイエンド メモリ領域 (ZONE_HIGHMEM) の 3 つの領域があると考えられています。
実際の物理メモリが小さい場合のカーネル空間の直接マッピング
この固定マッピング方法により、下位アドレスから上位アドレスへの関係が作成されることがわかります。仮想アドレスと物理アドレスは非常に単純です。つまり、カーネルの仮想アドレスと実際の物理アドレスです。値には固定オフセット PAGE_OFFSET のみがあるため、カーネルが仮想アドレスを使用して物理ページ フレームにアクセスするときは、減算するだけで済みます。仮想アドレスから PAGE_OFFSET を使用して実際の物理アドレスを取得すると、ページ テーブルを使用するよりも高速になります。
このアプローチはほぼ物理アドレスを直接使用するため、この種の固定マッピング方法に基づくカーネル空間は「物理メモリ空間」、または略して物理メモリとも呼ばれます。また、固定マッピング方式は線形マッピングであるため、この領域を線形マッピング領域とも呼びます。
もちろん、この場合 (コンピュータの実際の物理メモリが小さい場合)、カーネル固定マッピング領域は 1GB カーネル領域全体の一部のみを占有します。例: 32MB の実物理メモリを備えた x86 コンピュータ システムを構成する場合、カーネルの固定マッピング領域は PAGE_OFFSET~ (PAGE_OFFSET 0x02000000) の 32MB スペースです。では、カーネル空間に残っているカーネル仮想空間はどうすればよいのでしょうか?
もちろん、通常の仮想空間の管理方法に従って、ページテーブルの非線形マッピングには依然として物理メモリが使用されます。具体的には、1GBのカーネル空間全体から固定マッピング領域を削除し、残りの部分から先頭の8MBの分離領域を削除し、残りはユーザー空間と同様にマッピングされた通常の仮想メモリマッピング領域となります。 。この領域には、仮想アドレスと物理アドレスの間に固定的なマッピング関係がないだけでなく、カーネル関数 vmalloc() を呼び出すことによって動的メモリが取得されるため、この領域は vmalloc 割り当て領域と呼ばれます。以下の図:
実際の物理メモリ 32MB で構成された x86 コンピュータ システムの場合、vmalloc 割り当て領域の開始位置は PAGE_OFFSET 0x02000000 0x00800000 です。 ここで説明しましょう:ここで言及されているカーネル空間と物理ページ フレーム間の固定マッピングは、本質的にはカーネル ページと物理ページ フレームの「事前決定」です。ページは物理ページ フレームを「占有」します。」これらの物理ページ フレーム。つまり、仮想ページが実際に物理ページ フレームにアクセスする必要がある場合にのみ、仮想ページは物理ページ フレームにバインドされます。通常、物理ページ フレームが対応する仮想ページで使用されていない場合、そのページ フレームはユーザー空間と、後で導入されるカーネル kmalloc 割り当て領域で使用できます。
つまり、実物理メモリが小さいシステムでは、実メモリのサイズがカーネル空間の物理メモリ領域とvmalloc割り当て領域の境界となります。1GB カーネル空間全体について、スペースの先頭の 16MB も呼びます。 DMA ゾーン、つまり ZONE_DMA ゾーンは、以前のハードウェアが DMA スペースを物理メモリの下位 16MB スペースに固定していたため、残りのゾーンは通常ゾーン、つまり ZONE_NORMAL と呼ばれます。
カーネル空間のハイエンド メモリ
コンピュータ技術の発展により、カーネル空間の実際の物理メモリは、コンピュータはますます大型化しているため、カーネルの固定マッピング領域 (線形領域) はますます大きくなっています。 当然のことですが、制限を何も課さなければ、実際の物理メモリが 1GB に達すると、vmalloc 割り当て領域 (非線形領域) は存在しなくなります。したがって、vmalloc() を呼び出す以前に開発されたカーネル コードは使用できなくなりましたが、以前のカーネル コードとの互換性を保つために、これは許可されていません。
次の図は、このカーネル空間が直面する状況を示しています。 明らかに、上記の問題の理由は、実際の状況が次のとおりであることです。予想外 物理メモリは 1GB を超える可能性があるため、カーネルの固定マッピング領域の境界には制限が設定されておらず、実際の物理メモリに応じて増加することが許可されています。
上記の問題を解決する方法は次のとおりです。カーネル空間の固定マッピング領域の上限を、物理メモリの増加に伴って勝手に増加しないように制限します。 Linuxでは、カーネルマッピング領域の上限値は1G未満の定数high_memoryを超えてはいけないと規定されており、実際の物理メモリが大きい場合は3Gのhigh_memoryを境界として物理メモリ領域が決定されます。
例: x86 システムの場合、high_memory の値は 896M であるため、1GB カーネル空間の残りの 128MB は非線形マッピング領域になります。これにより、どのような場合でも、カーネルには初期のコードと互換性を保つのに十分な非線形マッピング領域が確保され、通常の仮想メモリの方法で 1GB を超える実際の物理メモリにアクセスできるようになります。言い換えれば、ハイエンド メモリの最も基本的な考え方は、アドレス空間のセクションを借用して一時的なアドレス マッピングを確立し、使用後に解放するというものです。この値に達すると、リサイクルしてすべての物理メモリにアクセスできます。 コンピュータに大容量の物理メモリがある場合、カーネル空間の概略図は次のとおりです。
従来、Linux ではカーネル空間のこの部分を 3G high_memory と呼んでいます。 ~4G-1 ハイエンド メモリ ゾーン (ZONE_HIGHMEM)。 要約すると、x86 構造のカーネル空間では、3 種類の領域 (3G から計算) は次のとおりです。さまざまなアプリケーション ターゲットに応じて、ハイエンド メモリは vmalloc 領域、永続マッピング領域に分割されます。および一時的なマッピング領域。 カーネル空間内のハイエンド メモリのレイアウトは次のとおりです。
カーネルは、この目的のために、PKMAP_BASE から始まる線形空間を特別に確保します。これは、永続的なカーネル マッピング領域であるハイエンド メモリのマッピングに使用されます。
永続的なカーネル マッピング領域では、関数 kmap() を呼び出すことで、物理ページ フレームとカーネル仮想ページの間の長期マッピングを確立できます。このスペースは通常 4MB で、最大 1024 ページ フレームをマップできます。この数は比較的まれです。したがって、ページ フレームの回転率を高めるために、関数 kunmap() を呼び出して、物理ページ フレームを解放する必要があります。もう使われていません。#一時マッピング領域
Linux ユーザー空間とカーネル空間 - ハイエンド メモリの詳細な説明
。#カーネル メモリ要求関数で要求の必要な記述を行うには、次のようにします。 Linux では、多くのメモリ割り当て修飾子 gfp が定義されています。それらは、動作修飾子、領域修飾子、タイプ修飾子です。
動作修飾子
__GFP_WAIT | |
__GFP_HIGH | |
__GFP_IO | |
__GFP_FS | |
##__GFP_COLD | アロケーターは、キャッシュから削除されようとしているページ フレームを使用する必要があります |
__GFP_NOWARN | アロケータは警告を発行しません |
__GFP_REPEAT | 割り当て失敗時の再割り当て |
__GFP_NOFAILT | 割り当て時の再割り当て失敗、成功するまで |
#__GFP_NORTRY | |
Linux のメイン カーネル メモリ割り当て領域修飾子
##Modifier__GFP_DMA | ZONE_DMA 領域からメモリを割り当てます | ||||||||||
__GFP_HIGHMEM | ZONE_HIGHMEM 領域からメモリを割り当てます | ||||||||||
类型修饰符类型修饰符实质上是上述所述修饰符的联合应用。也就是:将上述的某些行为修饰符和区修饰符,用“|”进行连接并另外取名的修饰符。这里就不多介绍了。 内核常用内存分配及地址映射函数 函数vmalloc()函数vmalloc()在vmalloc分配区分配内存,可获得虚拟地址连续,但并不保证其物理页框连续的较大内存。与物理空间的内存分配函数malloc()有所区别,vmalloc()分配的物理页不会被交换出去。函数vmalloc()的原型如下: void *vmalloc(unsigned long size) { return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL); } void *__vmalloc(unsigned long size, gfp_t gfp_mask, pgprot_t prot) { return kmalloc(size, (gfp_mask | __GFP_COMP) & ~__GFP_HIGHMEM); } 其中,参数size为所请求内存的大小,返回值为所获得内存虚拟地址指针。 与vmalloc()配套的释放函数如下: void vfree(const void *addr) { kfree(addr); } 其中,参数addr为待释放内存指针。 函数kmalloc()kmalloc()是内核另一个常用的内核分配函数,它可以分配一段未清零的连续物理内存页,返回值为直接映射地址。由kmalloc()可分配的内存最大不能超过32页。其优点是分配速度快,缺点是不能分配大于128KB的内存页(出于跨平台考虑)。 在linux/slab.h文件中,该函数的原型声明如下: static __always_inline void *kmalloc(size_t size, gfp_t flags) { struct kmem_cache *cachep; void *ret; if (__builtin_constant_p(size)) { int i = 0; if (!size) return ZERO_SIZE_PTR; #define CACHE(x) \ if (size <= x) \ goto found; \ else \ i++; #include <linux/kmalloc_sizes.h> #undef CACHE return NULL; found: #ifdef CONFIG_ZONE_DMA if (flags & GFP_DMA) cachep = malloc_sizes[i].cs_dmacachep; else #endif cachep = malloc_sizes[i].cs_cachep; ret = kmem_cache_alloc_notrace(cachep, flags); trace_kmalloc(_THIS_IP_, ret, size, slab_buffer_size(cachep), flags); return ret; } return __kmalloc(size, flags); } 其中,参数size为以字节为单位表示的所申请空间的大小;参数flags决定了所分配的内存适合什么场合。 与函数kmalloc()对应的释放函数如下: void kfree(const void *objp) { struct kmem_cache *c; unsigned long flags; trace_kfree(_RET_IP_, objp); if (unlikely(ZERO_OR_NULL_PTR(objp))) return; local_irq_save(flags); kfree_debugcheck(objp); c = virt_to_cache(objp); debug_check_no_locks_freed(objp, obj_size(c)); debug_check_no_obj_freed(objp, obj_size(c)); __cache_free(c, (void *)objp); local_irq_restore(flags); } 小结一下,kmalloc、vmalloc、malloc的区别:
也就是说:kmalloc、vmalloc这两个函数所分配的内存都处于内核空间,即从3GB~4GB;但位置不同,kmalloc()分配的内存处于3GB~high_memory(ZONE_DMA、ZONE_NORMAL)之间,而vmalloc()分配的内存在VMALLOC_START~4GB(ZONE_HIGHMEM)之间,也就是非连续内存区。一般情况下在驱动程序中都是调用kmalloc()来给数据结构分配内存,而vmalloc()用在为活动的交换区分配数据结构,为某些I/O驱动程序分配缓冲区,或为模块分配空间。 可参考文章:Kmalloc和Vmalloc的区别。 函数alloc_pages()与上述在虚拟空间分配内存的函数不同,alloc_pages()是在物理内存空间分配物理页框的函数,其原型如下: static inline struct page * alloc_pages(gfp_t gfp_mask, unsigned int order) { if (unlikely(order >= MAX_ORDER)) return NULL; return alloc_pages_current(gfp_mask, order); } 其中,参数order表示所分配页框的数目,该数目为2^order。order的最大值由include/Linux/Mmzone.h文件中的宏MAX_ORDER决定。参数gfp_mask为说明内存页框分配方式及使用场合。 函数返回值为页框块的第一个页框page结构的地址。 调用下列函数可以获得页框的虚拟地址: void *page_address(struct page *page) { unsigned long flags; void *ret; struct page_address_slot *pas; if (!PageHighMem(page)) return lowmem_page_address(page); pas = page_slot(page); ret = NULL; spin_lock_irqsave(&pas->lock, flags); if (!list_empty(&pas->lh)) { struct page_address_map *pam; list_for_each_entry(pam, &pas->lh, list) { if (pam->page == page) { ret = pam->virtual; goto done; } } } done: spin_unlock_irqrestore(&pas->lock, flags); return ret; } 使用函数alloc_pages()获得的内存应该使用下面的函数释放: void __free_pages(struct page *page, unsigned int order) { if (put_page_testzero(page)) { if (order == 0) free_hot_page(page); else __free_pages_ok(page, order); } } 函数kmap()kmap()是一个映射函数,它可以将一个物理页框映射到内核空间的可持久映射区。这种映射类似于内核ZONE_NORMAL的固定映射,但虚拟地址与物理地址的偏移不一定是PAGE_OFFSET。由于内核可持久映射区的容量有限(总共只有4MB),因此当内存使用完毕后,应该立即释放。 函数kmap()的函数原型如下: void *kmap(struct page *page) { might_sleep(); if (!PageHighMem(page)) return page_address(page); return kmap_high(page); } 小结 由于CPU的地址总线只有32位, 32的地址总线无论是从逻辑上还是从物理上都只能描述4G的地址空间(232=4Gbit),在物理上理论上最多拥有4G内存(除了IO地址空间,实际内存容量小于4G),逻辑空间也只能描述4G的线性地址空间。 为了合理的利用逻辑4G空间,Linux采用了3:1的策略,即内核占用1G的线性地址空间,用户占用3G的线性地址空间。所以用户进程的地址范围从0~3G,内核地址范围从3G~4G,也就是说,内核空间只有1G的逻辑线性地址空间。 Linux 物理メモリの容量が 1G 未満の場合、通常、カーネルは物理メモリをそのアドレス空間に線形にマップします (つまり 1 対 1 マッピング)。これにより、アクセス速度が向上します。ただし、Linux の物理メモリが 1G を超える場合、マッピングできるメモリは 1G のみで、残りの物理メモリはカーネルで管理できないため、リニア アクセス メカニズムでは不十分です。 Linuxの場合、カーネルアドレスはリニア領域とノンリニア領域に分かれており、リニア領域は最大896M、残りの128Mがノンリニア領域と規定されています。したがって、リニア領域によってマッピングされた物理メモリはローエンド メモリとなり、残りの物理メモリはハイエンド メモリと呼ばれます。線形領域とは異なり、非線形領域は事前にメモリにマッピングされませんが、使用時に動的にマッピングされます。 ローエンド メモリは 2 つの部分に分割されます: ZONE_DMA: カーネル空間から始まる 16MB、ZONE_NORMAL: カーネル空間の 16MB ~ 896MB (固定マッピング)。残りはハイエンド メモリです: ZONE_HIGHMEM: カーネル領域 896MB ~ 最後 (1G)。 アプリケーションのさまざまな目標に応じて、ハイエンド メモリは vmalloc 領域、永続マッピング領域、一時マッピング領域の 3 つの部分に分割されます。 vmalloc 領域は vmalloc() 関数を使用して割り当てられ、永続マッピング領域は allc_pages() を使用して対応するページを取得し、kmap() 関数を使用して直接マッピングされます。一時マッピング領域は通常、特別なニーズに使用されます。
|
以上がLinuxドライバーはどのスペースで実行されますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。