J'utilise Linux 5.1 sur un SoC Cyclone V, qui est un FPGA avec deux cœurs ARMv7 dans une puce. Mon objectif est de rassembler de nombreuses données à partir d'une interface externe et de diffuser (une partie de) ces données via une socket TCP. Le défi ici est que le débit de données est très élevé et pourrait être proche de saturer l'interface GbE. J'ai une implémentation de travail qui utilise uniquement des write()
appels à la socket, mais elle dépasse 55 Mo / s; environ la moitié de la limite théorique de GbE. J'essaie maintenant d'obtenir une transmission TCP sans copie pour augmenter le débit, mais je frappe un mur.
Pour extraire les données du FPGA dans l'espace utilisateur Linux, j'ai écrit un pilote de noyau. Ce pilote utilise un bloc DMA dans le FPGA pour copier une grande quantité de données d'une interface externe dans la mémoire DDR3 attachée aux cœurs ARMv7. Le pilote alloue cette mémoire en tant que groupe de tampons contigus de 1 Mo lorsqu'il est sondé à l'aide dma_alloc_coherent()
de GFP_USER
et les expose à l'application de l'espace utilisateur en les implémentant mmap()
dans un fichier /dev/
et en renvoyant une adresse à l'application en utilisant dma_mmap_coherent()
les tampons préalloués.
Jusqu'ici tout va bien; l'application de l'espace utilisateur voit des données valides et le débit est plus que suffisant à> 360 Mo / s avec de la place pour épargner (l'interface externe n'est pas assez rapide pour vraiment voir quelle est la limite supérieure).
Pour implémenter un réseau TCP sans copie, ma première approche a été d'utiliser SO_ZEROCOPY
sur le socket:
sent_bytes = send(fd, buf, len, MSG_ZEROCOPY);
if (sent_bytes < 0) {
perror("send");
return -1;
}
Cependant, cela se traduit par send: Bad address
.
Après avoir googlé un peu, ma deuxième approche a été d'utiliser un tuyau et a splice()
suivi par vmsplice()
:
ssize_t sent_bytes;
int pipes[2];
struct iovec iov = {
.iov_base = buf,
.iov_len = len
};
pipe(pipes);
sent_bytes = vmsplice(pipes[1], &iov, 1, 0);
if (sent_bytes < 0) {
perror("vmsplice");
return -1;
}
sent_bytes = splice(pipes[0], 0, fd, 0, sent_bytes, SPLICE_F_MOVE);
if (sent_bytes < 0) {
perror("splice");
return -1;
}
Cependant, le résultat est le même: vmsplice: Bad address
.
Notez que si je remplace l'appel à vmsplice()
ou send()
à une fonction qui imprime simplement les données pointées par buf
(ou un send()
sans MSG_ZEROCOPY
), tout fonctionne très bien; les données sont donc accessibles à l'espace utilisateur, mais les appels vmsplice()
/ send(..., MSG_ZEROCOPY)
semblent incapables de les gérer.
Qu'est-ce que j'oublie ici? Existe-t-il un moyen d'utiliser l'envoi TCP sans copie avec une adresse de l'espace utilisateur obtenue à partir d'un pilote de noyau via dma_mmap_coherent()
? Y a-t-il une autre approche que je pourrais utiliser?
MISE À JOUR
J'ai donc plongé un peu plus profondément dans le sendmsg()
MSG_ZEROCOPY
chemin dans le noyau, et l'appel qui échoue finalement l'est get_user_pages_fast()
. Cet appel renvoie -EFAULT
car check_vma_flags()
trouve l' VM_PFNMAP
indicateur défini dans le vma
. Cet indicateur est apparemment défini lorsque les pages sont mappées dans l'espace utilisateur à l'aide de remap_pfn_range()
ou dma_mmap_coherent()
. Ma prochaine approche consiste à trouver un autre moyen d'accéder à mmap
ces pages.