Comment allouer un tampon DMA sauvegardé par HugePages de 1 Go dans un module kernel Linux?

J’essaie d’allouer un tampon DMA pour une charge de travail HPC. Il nécessite 64 Go d’espace tampon. Entre le calcul, certaines données sont déchargées sur une carte PCIe. Plutôt que de copier des données dans un tas de tampons dinky de 4 Mo donnés par pci_alloc_consistent, je voudrais juste créer 64 tampons de 1 Go, soutenus par HugePages de 1 Go.

Quelques informations générales: version du kernel: CentOS 6.4 / 2.6.32-358.el6.x86_64 options de démarrage du kernel: hugepagesz = 1g hugepages = 64 default_hugepagesz = 1g

partie pertinente de / proc / meminfo: AnonHugePages: 0 Ko HugePages_Total: 64 HugePages_Free: 64 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 1048576 Ko DirectMap4k: 848 Ko DirectMap2M: 2062336 Ko DirectMap1G: 132120576 Ko

Je peux monter -t hugetlbfs nodev / mnt / hugepages. CONFIG_HUGETLB_PAGE est vrai. MAP_HUGETLB est défini.

J’ai lu quelques informations sur l’utilisation de libhugetlbfs pour appeler get_huge_pages () dans l’espace utilisateur, mais idéalement, ce tampon serait alloué dans l’espace du kernel. J’ai essayé d’appeler do_mmap () avec MAP_HUGETLB mais cela ne semblait pas changer le nombre d’énormes pages gratuites, donc je ne pense pas que ce soit en fait une sauvegarde des pages massives.

Donc je suppose ce que je veux dire, y a-t-il un moyen de mapper un tampon sur un HugePage de 1 Go dans l’espace kernel, ou faut-il le faire dans l’espace utilisateur? Ou si quelqu’un sait d’une autre manière, je peux obtenir une quantité énorme (1-64 Go) de mémoire physique contiguë disponible en tant que tampon du kernel?

Ceci n’est généralement pas fait dans l’espace kernel, donc pas trop d’exemples.

Comme toute autre page, des pages énormes sont allouées avec alloc_pages, à la mélodie:

 struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER); 

HPAGE_PMD_ORDER est une macro définissant un ordre d’une page unique énorme en termes de pages normales. Ce qui précède implique que les énormes pages transparentes sont activées dans le kernel.

Ensuite, vous pouvez continuer à mapper le pointeur de page obtenu de la manière habituelle avec kmap ().

Disclaimer: Je n’ai jamais essayé moi-même, alors vous devrez peut-être faire des essais. Une chose à vérifier est la suivante: HPAGE_PMD_SHIFT représente un ordre d’une plus petite page “énorme”. Si vous souhaitez utiliser ces pages géantes de 1 Go, vous devrez probablement essayer un autre ordre, probablement PUD_SHIFT – PAGE_SHIFT.

PROBLÈME

  1. Normalement, si vous voulez allouer un tampon DMA ou obtenir une adresse physique, cela se fait dans l’espace du kernel, car le code utilisateur ne devrait jamais avoir à se déplacer avec des adresses physiques.
  2. Hugetlbfs ne fournit que des mappages d’espace utilisateur pour allouer des pages de 1 Go et obtenir des adresses virtuelles d’espace utilisateur
  3. Aucune fonction n’existe pour mapper une adresse virtuelle de page énorme utilisateur à une adresse physique

EUREKA

Mais la fonction existe! Enfouie profondément dans le code source du kernel 2.6, cette fonction permet d’obtenir une page de structure à partir d’une adresse virtuelle, marquée “juste pour les tests” et bloquée avec #if 0:

 #if 0 /* This is just for testing */ struct page * follow_huge_addr(struct mm_struct *mm, unsigned long address, int write) { unsigned long start = address; int length = 1; int nr; struct page *page; struct vm_area_struct *vma; vma = find_vma(mm, addr); if (!vma || !is_vm_hugetlb_page(vma)) return ERR_PTR(-EINVAL); pte = huge_pte_offset(mm, address); /* hugetlb should be locked, and hence, prefaulted */ WARN_ON(!pte || pte_none(*pte)); page = &pte_page(*pte)[vpfn % (HPAGE_SIZE/PAGE_SIZE)]; WARN_ON(!PageHead(page)); return page; } 

SOLUTION: Étant donné que la fonction ci-dessus n’est pas réellement compilée dans le kernel, vous devrez l’append à votre source de pilote.

FLUX DE TRAVAIL DU CÔTÉ UTILISATEUR

  1. Allouer 1 Go d’énormes pages au démarrage avec les options de démarrage du kernel
  2. Appelez get_huge_pages () avec hugetlbfs pour obtenir le pointeur de l’espace utilisateur (adresse virtuelle)
  3. Passer l’adresse virtuelle de l’utilisateur (pointeur normal converti en non signé long) au pilote ioctl

FLUX DE TRAVAIL DE CONDUCTEUR DE KERNEL

  1. Accepter l’adresse virtuelle de l’utilisateur via ioctl
  2. Appelez follow_huge_addr pour obtenir la page de la structure *
  3. Appelez page_to_phys sur la page de la structure * pour obtenir l’adresse physique
  4. Fournir une adresse physique à l’appareil pour DMA
  5. Appelez kmap sur la page struct * si vous souhaitez également un pointeur virtuel de kernel

AVERTISSEMENT

  • Les étapes ci-dessus sont rappelées plusieurs années plus tard. J’ai perdu l’access au code source d’origine. Faites votre diligence raisonnable et assurez-vous de ne pas oublier une étape.
  • La seule raison pour laquelle cela fonctionne est que des pages de 1 Go sont allouées au démarrage et que leurs adresses physiques sont définitivement verrouillées. N’essayez pas de mapper une adresse virtuelle utilisateur non-1GBhugepage dans une adresse physique DMA! Vous allez passer un mauvais moment!
  • Testez soigneusement votre système pour vérifier que vos énormes pages de 1 Go sont effectivement bloquées dans la mémoire physique et que tout fonctionne correctement. Ce code fonctionnait parfaitement sur mon installation, mais il y a un grand danger si quelque chose ne va pas.
  • Ce code ne fonctionne que sur une architecture x86 / x64 (adresse physique == adresse du bus) et sur la version du kernel 2.6.XX. Il y a peut-être un moyen plus facile de le faire sur les versions ultérieures du kernel, ou cela peut être complètement impossible maintenant.