Ivshmem是虛擬機內部共享內存的pci設備。虛擬機之間實現內存共享是把內存映射成guest內的pci設備來實現的。css
從代碼分析和實際驗證,guest與guest之間能夠實現中斷與非中斷2種模式下的通訊, host與guest之間只支持非中斷模式的通訊。node
BAR是PCI配置空間中從0x10 到 0x24的6個register,用來定義PCI須要的配置空間大小以及配置PCI設備佔用的地址空間,X86中地址空間分爲MEM和IO兩類,所以PCI 的BAR在bit0來表示該設備是映射到memory仍是IO,bar的bit0是隻讀的,bit1保留位,bit2 中0表示32位地址空間,1表示64位地址空間,其他的bit用來表示設備須要佔用的地址空間大小與設備起始地址。ios
ivshmem設備支持3個PCI基地址寄存器。BAR0是1kbyte 的MMIO區域,支持寄存器。根據計算能夠獲得,設備當前支持3個32bits寄存器(下文介紹),還有一個寄存器爲每一個guest都有,最多支持253個guest(一共256*4=1kbyte),實際默認爲16。數組
BAR1用於MSI-X,BAR2用來從host中映射共享內存體。BAR2的大小經過命令行指定,必須是2的次方。數據結構
共享內存server是在host上運行的一個應用程序,每啓動一個vm,server會指派給vm一個id號,而且將vm的id號和分配的eventfd文件描述符一塊兒發給qemu進程。Id號在收發數據時用來標識vm,guests之間經過eventfd來通知中斷。每一個guest都在與本身id所綁定的eventfd上偵聽,而且使用其它eventfd來向其它guest發送中斷。app
共享內存服務者代碼在nahanni的ivshmem_server.c,後文給出分析。socket
Ivshmem設備共有4種類型的寄存器,寄存器用於guest之間共享內存的同步,mask和status在pin中斷下使用,msi下不使用:tcp
/*registers for the Inter-VM shared memory device */ide
enumivshmem_registers {函數
INTRMASK = 0,
INTRSTATUS = 4,
IVPOSITION = 8,
DOORBELL = 12,
};
Mask寄存器:
與中斷狀態按位與,若是非0則觸發一箇中斷。所以能夠經過設置mask的第一bit爲0來屏蔽中斷。
Status寄存器:
當中斷髮生(pin中斷下doorbell被設置),目前qemu驅動所實現的寄存器被設置爲1。因爲status代碼只會設1,因此mask也只有第一個bit會有左右,筆者理解可經過修改驅動代碼實現status含義的多元化。
IVPosition寄存器:
IVPosition是隻讀的,報告了guest id號碼。Guest id是非負整數。id只會在設備就緒是被設置。若是設備沒有準備好,IVPosition返回-1。應用程序必須確保他們有有效的id後纔開始使用共享內存。
Doorbell寄存器:
經過寫本身的doorbell寄存器能夠向其它guest發送中斷。爲了向其它guest發送中斷,須要向其它guest的doorbell寫入值,doorbell寄存器爲32bit,分爲2個16bit域。高16bit是guest id是接收中斷的guestid,低16bit是所觸發的中斷向量。一個指定guest id的doorbell在mmio區域的偏移等於:
guest_id * 32 + Doorbell
寫入doorbell的語義取決於設備是使用msi仍是pin的中斷,下文介紹。
非中斷模式直接把虛擬pci設備當作一個共享內存進行操做,中斷模式則會操做虛擬pci的寄存器進行通訊,數據的傳輸都會觸發一次虛擬pci中斷並觸發中斷回調,使接收方顯式感知到數據的到來,而不是一直阻塞在read。
ivshmem中斷模式分爲Pin-based 中斷和msi中斷:
MSI的全稱是Message Signaled Interrupt。MSI出如今PCI 2.2和PCIe的規範中,是一種內部中斷信號機制。傳統的中斷都有專門的中斷pin,當中斷信號產生時,中斷PIN電平產生變化(通常是拉低)。INTx就是傳統的外部中斷觸發機制,它使用專門的通道來產生控制信息。然而PCIe並無多根獨立的中斷PIN,因而使用特殊的信號來模擬中斷PIN的置位和復位。MSI容許設備向一段指定的MMIO地址空間寫一小段數據,而後chipset以此產生相應的中斷給CPU。
從電氣機械的角度,MSI減小了對interrupt pin個數的需求,增長了中斷號的數量,傳統的PCI中斷只容許每一個device擁有4箇中斷,而且因爲這些中斷都是共享的,大部分device都只有一箇中斷,MSI容許每一個device有1,2,4,8,16甚至32箇中斷。
使用MSI也有一點點性能上的優點。使用傳統的PIN中斷,當中斷到來時,程序去讀內存獲取數據時有可能會產生衝突。其緣由device的數據主要經過DMA來傳輸,而在PIN中斷到達時,DMA傳輸還未能完成,此時cpu不能獲取到數據,只能空轉。而MSI不會存在這個問題,由於MSI都是發生在DMA傳輸完成以後的。
因爲具備瞭如上特性,ivshmem在執行pin中斷時,則寫入低16位的值(就是1)觸發對目標guest的中斷。若是使用Msi,則ivshmem設備支持多msi向量。低16bit寫入值爲0到Guest所支持的最大向量。低16位寫入的值就是目標guest上將會觸發的msi向量。Msi向量在vm啓動時配置。Mis不設置status位,所以除了中斷自身,全部信息都經過共享內存區域通訊。因爲設備支持多msi向量,這樣可使用不一樣的向量來表示不一樣的事件。這些向量的含義由用戶來定。
host上Linux內核能夠經過將tmpfs掛載到/dev/shm,從而經過/dev/shm來提供共享內存做爲bar2映射的共享內存體。
mount tmpfs /dev/shm -t tmpfs -osize=32m
也可經過shm_open+ftruncate建立一個共享內存文件/tmp/nahanni。
在中斷模式下,qemu啓動前會啓動nahanni的ivshmem_server進程,該進程守候等待qemu的鏈接,待socket鏈接創建後,經過socket指派給每一個vm一個id號(posn變量),而且將id號同eventfd文件描述符一塊兒發給qemu進程(一個efd表示一箇中斷向量,msi下有多個efd)。
ivshmem_server啓動方式以下:
./ivshmem_server -m 64 -p/tmp/nahanni &
其中-m所帶參數爲總共享內存大小單位(M),-p表明共享內存體,-n代碼msi模式中斷向量個數。
在qemu一端經過–chardev socket創建socket鏈接,並經過-deviceivshmem創建共享內存設備。
./qemu-system-x86_64 -hda mg -L /pc-bios/ --smp 4 –chardev socket,path=/tmp/nahanni,id=nahanni-device ivshmem,chardev=nahanni,size=32m,msi=off -serial telnet:0.0.0.0:4000,server,nowait,nodelay-enable-kvm&
Server端經過select偵聽一個qemu上socket的鏈接。 Qemu端啓動時須要設置-chardevsocket,path=/tmp/nahanni,id=nahanni,經過該設置qemu經過查找chardev註冊類型register_types會調用qemu_chr_open_socket->unix_connect_opts,實現與server之間創建socket鏈接,server的add_new_guest會指派給每一個vm一個id號,而且將id號同一系列eventfd文件描述符一塊兒發給qemu進程,qemu間經過高效率的eventfd方式通訊。
voidadd_new_guest(server_state_t * s) {
struct sockaddr_un remote;
socklen_t t = sizeof(remote);
long i, j;
int vm_sock;
long new_posn;
long neg1 = -1;
//等待qemu的-chardev socket,path=/tmp/nahanni,id=nahanni鏈接
vm_sock = accept(s->conn_socket, (structsockaddr *)&remote, &t);// qemu 啓動時查找chardev註冊類型register_types調用qemu_chr_open_socket->unix_connect_opts實現server創建socket鏈接
if ( vm_sock == -1 ) {
perror("accept");
exit(1);
}
new_posn = s->total_count;
//當new_posn==s->nr_allocated_vms表明一個新的posn的加入
if (new_posn == s->nr_allocated_vms) {
printf("increasing vmslots\n");
s->nr_allocated_vms = s->nr_allocated_vms* 2;
if (s->nr_allocated_vms < 16)
s->nr_allocated_vms = 16;
//server_state_t分配新new_posn信息結構
s->live_vms =realloc(s->live_vms,
s->nr_allocated_vms *sizeof(vmguest_t));
if (s->live_vms == NULL) {
fprintf(stderr, "reallocfailed - quitting\n");
exit(-1);
}
}
//新結構的posn
s->live_vms[new_posn].posn = new_posn;
printf("[NC] Live_vms[%ld]\n",new_posn);
s->live_vms[new_posn].efd = (int *) malloc(sizeof(int));
//分配新結構的新結構的msi_vectors的efd,對於通用pci模式msi_vectors=0
for (i = 0; i < s->msi_vectors; i++){
s->live_vms[new_posn].efd[i] =eventfd(0, 0);
printf("\tefd[%ld] = %d\n",i, s->live_vms[new_posn].efd[i]);
}
s->live_vms[new_posn].sockfd = vm_sock;
s->live_vms[new_posn].alive = 1;
//將new_posn和efd等發送給新的vm_sock
sendPosition(vm_sock, new_posn);
sendUpdate(vm_sock, neg1, sizeof(long),s->shm_fd);
printf("[NC] trying to send fds to newconnection\n");
sendRights(vm_sock, new_posn,sizeof(new_posn), s->live_vms, s->msi_vectors);
printf("[NC] Connected (count =%ld).\n", new_posn);
//將new_posn的加入信息告知老的vm_sock
for (i = 0; i < new_posn; i++) {
if (s->live_vms[i].alive) {
// ping all clients that a newclient has joined
printf("[UD] sending fd[%ld]to %ld\n", new_posn, i);
for (j = 0; j msi_vectors; j++) {
printf("\tefd[%ld] =[%d]", j, s->live_vms[new_posn].efd[j]);
sendUpdate(s->live_vms[i].sockfd, new_posn,
sizeof(new_posn),s->live_vms[new_posn].efd[j]);
}
printf("\n");
}
}
//total_count保存鏈接總次數
s->total_count++;
}
Qemu側Pci設備ivshmem初始化時經過ivshmem_read接收server端發來的posn和efd信息,在經過create_eventfd_chr_device建立eventfd字符設備。
staticvoid ivshmem_read(void *opaque, const uint8_t * buf, int flags)
{
… //相關參數檢查
/* if the position is -1, then it's sharedmemory region fd */
//-1表示共享內存的fd
if (incoming_posn == -1) {
void * map_ptr;
s->max_peer = 0;
if (check_shm_size(s, incoming_fd) ==-1) {
exit(-1);
}
/* mmap the region and map into theBAR2 */
map_ptr = mmap(0, s->ivshmem_size,PROT_READ|PROT_WRITE, MAP_SHARED,
incoming_fd, 0);
//建立 MemoryRegions結構
memory_region_init_ram_ptr(&s->ivshmem, OBJECT(s),
"ivshmem.bar2", s->ivshmem_size, map_ptr);
vmstate_register_ram(&s->ivshmem, DEVICE(s));
IVSHMEM_DPRINTF("guest h/w addr =%" PRIu64 ", size = %" PRIu64 "\n",
s->ivshmem_attr,s->ivshmem_size);
//添加到guestos的地址空間
memory_region_add_subregion(&s->bar,0, &s->ivshmem);
/* only store the fd if it issuccessfully mapped */
s->shm_fd = incoming_fd;
return;
}
/* each guest has an array of eventfds, andwe keep track of how many
* guests for each VM */
guest_max_eventfd =s->peers[incoming_posn].nb_eventfds;
if (guest_max_eventfd == 0) {
/* one eventfd per MSI vector */
s->peers[incoming_posn].eventfds =g_new(EventNotifier, s->vectors);
}
/* this is an eventfd for a particularguest VM */
IVSHMEM_DPRINTF("eventfds[%ld][%d] =%d\n", incoming_posn,
guest_max_eventfd, incoming_fd);
//初始化eventfd
event_notifier_init_fd(&s->peers[incoming_posn].eventfds[guest_max_eventfd],
incoming_fd);
/* increment count for particular guest */
s->peers[incoming_posn].nb_eventfds++;
/* keep track of the maximum VM ID */
if (incoming_posn > s->max_peer) {
s->max_peer = incoming_posn;
}
if (incoming_posn == s->vm_id) {
//接收端建立eventfd的字符設備
s->eventfd_chr[guest_max_eventfd] = create_eventfd_chr_device(s,
&s->peers[s->vm_id].eventfds[guest_max_eventfd],
guest_max_eventfd);
}
if (ivshmem_has_feature(s,IVSHMEM_IOEVENTFD)) {
ivshmem_add_eventfd(s, incoming_posn,guest_max_eventfd);
}
}
create_eventfd_chr_device建立並初始化efd接收設備:
staticCharDriverState* create_eventfd_chr_device(void * opaque, EventNotifier *n,
int vector)
{
if (ivshmem_has_feature(s, IVSHMEM_MSI)) {
s->eventfd_table[vector].pdev =PCI_DEVICE(s);
s->eventfd_table[vector].vector =vector;
//msi字符設備註冊
qemu_chr_add_handlers(chr,ivshmem_can_receive, fake_irqfd,
ivshmem_event,&s->eventfd_table[vector]);
} else {
//pin字符設備註冊
qemu_chr_add_handlers(chr, ivshmem_can_receive, ivshmem_receive,
ivshmem_event, s);
}
Eventfd創建流程圖以下:
在非中斷版本中,無需經過–chardevsocket創建鏈接,但一樣須要支持-device ivshmem創建共享內存:
./qemu-system-x86_64-dyn -hda Img -L /pc-bios/ --smp 4 -device ivshmem,shm=nahanni,size=32m -serial telnet:0.0.0.0:4001,server,nowait,nodelay&
其中-device ivshmem後的參數經過ivshmem_properties被傳遞給了IVShmemState結構:
staticProperty ivshmem_properties[] = {//傳入IVShmemState參數
DEFINE_PROP_CHR("chardev",IVShmemState, server_chr),
DEFINE_PROP_STRING("size", IVShmemState,sizearg),
DEFINE_PROP_UINT32("vectors",IVShmemState, vectors, 1),
DEFINE_PROP_BIT("ioeventfd",IVShmemState, features, IVSHMEM_IOEVENTFD, false),
DEFINE_PROP_BIT("msi",IVShmemState, features, IVSHMEM_MSI, true),
DEFINE_PROP_STRING("shm",IVShmemState, shmobj),///dev/shm/nahanni
DEFINE_PROP_STRING("role",IVShmemState, role),
DEFINE_PROP_END_OF_LIST(),
};
IVShmemState是用來保存ivshmem狀態的結構,用做pci設備建立:
typedefstruct IVShmemState {
PCIDevice dev;
uint32_t intrmask;
uint32_t intrstatus;
uint32_t doorbell;
CharDriverState **eventfd_chr; //保存efd字符設備
CharDriverState *server_chr;//保存server字符設備
MemoryRegion ivshmem_mmio;//bar0映射的內存區,MemoryRegion結構爲qemu分配內存使用
MemoryRegion bar;//在guest沒有分配實際內存空間以前使用
MemoryRegionivshmem;//bar2的實際內存區
uint64_tivshmem_size; //共享內存區大小
int shm_fd; /* shared memory filedescriptor 共享內存文件描述符*/
Peer *peers;//對應guest數組的指針,保存其中斷向量
int nb_peers;// 預備多少個guest空間,默認16
int max_peer; //最大支持的空間數
int vm_id;//vm_id<max_peer <="" p="" style="word-wrap: break-word;">
uint32_t vectors;//msi註冊的中斷向量數
uint32_t features;//包含IVSHMEM_MSI、IVSHMEM_IOEVENTFD等特性
EventfdEntry *eventfd_table;// eventfd表
Error *migration_blocker;
char * shmobj;//nahanni
char * sizearg;//共享體大小含單位
char * role;
int role_val; /* scalar to avoid multiple stringcomparisons */
}IVShmemState;
Qemu經過ivshmem設備類型的註冊:ivshmem_class_init->pci_ivshmem_init
pci_ivshmem_init對傳入參數進行合法性檢查並申請並初始化IVShmemState結構
staticint pci_ivshmem_init(PCIDevice *dev)
{
IVShmemState *s = DO_UPCAST(IVShmemState,dev, dev);
uint8_t *pci_conf;
//一些參數合法性檢查
if (s->sizearg == NULL)
s->ivshmem_size = 4 << 20; /*4 MB default *//*若是沒有指定大小*/
else {
s->ivshmem_size =ivshmem_get_size(s);//讀出sizearg連同單位,寫入IVShmemState
}
//註冊可遷移虛擬pci設備ivshmem
register_savevm(&s->dev.qdev,"ivshmem", 0, 0, ivshmem_save, ivshmem_load, dev);
/* IRQFD requires MSI */
if (ivshmem_has_feature(s,IVSHMEM_IOEVENTFD) &&
!ivshmem_has_feature(s, IVSHMEM_MSI)){//IVSHMEM_IOEVENTFD須要與IVSHMEM_MSI同時配置
fprintf(stderr, "ivshmem:ioeventfd/irqfd requires MSI\n");
exit(1);
}
/* check that role is reasonable */
if (s->role) {
if (strncmp(s->role,"peer", 5) == 0) {
s->role_val = IVSHMEM_PEER;
} else if (strncmp(s->role,"master", 7) == 0) {
s->role_val = IVSHMEM_MASTER;
} else {
fprintf(stderr, "ivshmem:'role' must be 'peer' or 'master'\n");
exit(1);
}
} else {
s->role_val = IVSHMEM_MASTER; /*default */
}
if (s->role_val == IVSHMEM_PEER) {
error_set(&s->migration_blocker,QERR_DEVICE_FEATURE_BLOCKS_MIGRATION,
"peer mode","ivshmem");
migrate_add_blocker(s->migration_blocker);
}
pci_conf = s->dev.config;
pci_conf[PCI_COMMAND] = PCI_COMMAND_IO |PCI_COMMAND_MEMORY;
pci_config_set_interrupt_pin(pci_conf, 1);
s->shm_fd = 0;
/*BAR0的MemoryRegion結構初始化,qemu對共享內存區的讀寫就由ivshmem_mmio_ops實現*/
memory_region_init_io(&s->ivshmem_mmio, &ivshmem_mmio_ops, s,
"ivshmem-mmio", IVSHMEM_REG_BAR_SIZE);
/* region for registers*/
/*pci的BAR0地址的映射,第二參數表示bar0*/
pci_register_bar(&s->dev, 0,PCI_BASE_ADDRESS_SPACE_MEMORY,
&s->ivshmem_mmio);
/*BAR2的MemoryRegion結構初始化*/
memory_region_init(&s->bar,"ivshmem-bar2-container", s->ivshmem_size);
……..
if((s->server_chr != NULL) && (strncmp(s->server_chr->filename, "unix:",5) == 0)) {
//中斷模式
pci_register_bar(dev, 2,s->ivshmem_attr, &s->bar); /*pci的BAR2地址的映射*/
s->eventfd_chr =g_malloc0(s->vectors * sizeof(CharDriverState *));
qemu_chr_add_handlers(s->server_chr,ivshmem_can_receive, ivshmem_read,
ivshmem_event, s);
//非中斷模式
int fd;
if (s->shmobj == NULL) {
fprintf(stderr, "Must specify'chardev' or 'shm' to ivshmem\n");
}
IVSHMEM_DPRINTF("using shm_open(shm object = %s)\n", s->shmobj);
//獲取共享內存體文件句柄
if ((fd = shm_open(s->shmobj,O_CREAT|O_RDWR|O_EXCL,
S_IRWXU|S_IRWXG|S_IRWXO)) > 0) {
/* truncate file to length PCIdevice's memory */
if (ftruncate(fd, s->ivshmem_size)!= 0) {
fprintf(stderr, "ivshmem:could not truncate shared file\n");
}
} else if ((fd = shm_open(s->shmobj,O_CREAT|O_RDWR,
S_IRWXU|S_IRWXG|S_IRWXO)) < 0) {
fprintf(stderr, "ivshmem:could not open shared file\n");
exit(-1);
}
//檢查
if (check_shm_size(s, fd) == -1) {
exit(-1);
}
/*建立並映射pci共享內存bar2*/
create_shared_memory_BAR(s, fd);
}
ivshmem_mmio_ops所註冊了bar0讀寫函數,其中對doorbell的寫操做ivshmem_io_write-> event_notifier_set轉換爲對eventfd的寫操做:
intevent_notifier_set(EventNotifier *e)
{
… …
ret = write(e->wfd, &value,sizeof(value));
} while (ret < 0 && errno ==EINTR);
create_shared_memory_BAR也是共享內存的核心函數,對於非中斷模式只需將bar2映射進guestos地址空間,即完成了內存共享:
staticvoid create_shared_memory_BAR(IVShmemState *s, int fd) {
void * ptr;
s->shm_fd = fd;
ptr = mmap(0, s->ivshmem_size,PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
//建立 Memory Regions結構
memory_region_init_ram_ptr(&s->ivshmem, "ivshmem.bar2",
s->ivshmem_size, ptr);
vmstate_register_ram(&s->ivshmem,&s->dev.qdev);
//添加到guestos的地址空間
memory_region_add_subregion(&s->bar,0, &s->ivshmem);
//完成地址空間向bar寄存器的註冊
pci_register_bar(&s->dev, 2,PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar);
}
每一個MemoryRegions結構經過構造函數memory_region_init*()來建立,並經過析構函數memory_region_destrory()來銷燬,而後,經過memory_region_add_subregion()將其添加到guestos的地址空間中,並經過memory_region_del_subregion()從地址空間中刪除,另外,每一個MR的屬性在任何地方均可以被改變。以前說過pci的bar寄存器用來記錄設備須要佔用的地址空間大小與設備起始地址,pci_register_bar最後是將該地址寫入bar寄存器。
至此在qemu完成了模擬一個pci設備並實現內存映射。
下一步在guest os上經過本身寫一個模塊,用來驅動這個pci設備,首先註冊kvm_ivshmem這個字符設備,獲得主設備號。
register_chrdev(0, "kvm_ivshmem", &kvm_ivshmem_ops);
而且實現該pci字符設備的文件操做:
staticconst struct file_operations kvm_ivshmem_ops = {
.owner = THIS_MODULE,
.open =kvm_ivshmem_open,
.mmap =kvm_ivshmem_mmap,
.read =kvm_ivshmem_read,
.ioctl = kvm_ivshmem_ioctl,
.write = kvm_ivshmem_write,
.llseek = kvm_ivshmem_lseek,
.release = kvm_ivshmem_release,
};
其中以設備寫操做爲例:
staticssize_t kvm_ivshmem_write(struct file * filp, const char * buffer,
size_tlen, loff_t * poffset)
{
int bytes_written = 0;
unsigned long offset;
offset = *poffset;
printk(KERN_INFO "KVM_IVSHMEM:trying to write\n");
//下文會介紹,指的bar2映射到線性地址空間,ioctl中能夠實現對kvm_ivshmem_dev.regs的操做,即對bar0的操做。
if (!kvm_ivshmem_dev.base_addr) {
printk(KERN_ERR"KVM_IVSHMEM: cannot write to ioaddr (NULL)\n");
return 0;
}
if (len >kvm_ivshmem_dev.ioaddr_size - offset) {
len =kvm_ivshmem_dev.ioaddr_size - offset;
}
printk(KERN_INFO "KVM_IVSHMEM:len is %u\n", (unsigned) len);
if (len == 0) return 0;
//將數據拷貝至kvm_ivshmem_dev.base_addr+offset地址
bytes_written =copy_from_user(kvm_ivshmem_dev.base_addr+offset,
buffer,len);
if (bytes_written > 0) {
return -EFAULT;
}
printk(KERN_INFO "KVM_IVSHMEM:wrote %u bytes at offset %lu\n", (unsigned) len, offset);
*poffset += len;
return len;
}
內核模塊註冊kvm_ivshmem這個字符設備以後,經過pci_register_driver(&kvm_ivshmem_pci_driver)實現字符型pci設備註冊,隨後由pci_driver數據結構中的probe函數指針所指向的偵測函數來初始化該PCI設備:
staticint kvm_ivshmem_probe_device (struct pci_dev *pdev,
conststruct pci_device_id * ent) {
int result;
printk("KVM_IVSHMEM: Probing forKVM_IVSHMEM Device\n");//插入模塊時,探測pci設備,pci設備在啓動qemu時已存在
result = pci_enable_device(pdev);//初始化設備,喚醒設備
if (result) {
printk(KERN_ERR "Cannotprobe KVM_IVSHMEM device %s: error %d\n",
pci_name(pdev), result);
return result;
}
result =pci_request_regions(pdev, "kvm_ivshmem");//根據pci_register_bar註冊的地址空間申請內存空間,配置ivshmem的memory資源
if (result < 0) {
printk(KERN_ERR"KVM_IVSHMEM: cannot request regions\n");
goto pci_disable;
} else printk(KERN_ERR"KVM_IVSHMEM: result is %d\n", result);
//bar2的映射
kvm_ivshmem_dev.ioaddr= pci_resource_start(pdev, 2);// bar2的映射內存的啓始地址
kvm_ivshmem_dev.ioaddr_size= pci_resource_len(pdev, 2);// bar2的映射內存的大小
kvm_ivshmem_dev.base_addr= pci_iomap(pdev, 2, 0);//將bar2映射到線性地址空間
printk(KERN_INFO "KVM_IVSHMEM:iomap base = 0x%lu \n",
(unsignedlong) kvm_ivshmem_dev.base_addr);
if (!kvm_ivshmem_dev.base_addr) {
printk(KERN_ERR"KVM_IVSHMEM: cannot iomap region of size %d\n",
kvm_ivshmem_dev.ioaddr_size);
goto pci_release;
}
printk(KERN_INFO "KVM_IVSHMEM:ioaddr = %x ioaddr_size = %d\n",
kvm_ivshmem_dev.ioaddr,kvm_ivshmem_dev.ioaddr_size);
//bar0的映射
kvm_ivshmem_dev.regaddr= pci_resource_start(pdev, 0);// bar0的映射內存的啓始地址
kvm_ivshmem_dev.reg_size= pci_resource_len(pdev, 0);// bar0的映射內存的大小
kvm_ivshmem_dev.regs= pci_iomap(pdev, 0, 0x100);//將bar0映射到線性地址空間
kvm_ivshmem_dev.dev= pdev;
if (!kvm_ivshmem_dev.regs) {
printk(KERN_ERR"KVM_IVSHMEM: cannot ioremap registers of size %d\n",
kvm_ivshmem_dev.reg_size);
goto reg_release;
}
/*設置IntrMask使能*/
writel(0xffffffff,kvm_ivshmem_dev.regs + IntrMask);
/* by default initialize semaphore to0 */
sema_init(&sema, 0);
init_waitqueue_head(&wait_queue);
event_num = 0;
//對於msix模式根據所註冊向量nvectors數,分別註冊中斷回調kvm_ivshmem_interrupt
if(request_msix_vectors(&kvm_ivshmem_dev, 4) != 0) {
printk(KERN_INFO"regular IRQs\n");
//對於普通中斷模式註冊中斷回調kvm_ivshmem_interrupt
if (request_irq(pdev->irq,kvm_ivshmem_interrupt, IRQF_SHARED,
"kvm_ivshmem",&kvm_ivshmem_dev)) {
printk(KERN_ERR"KVM_IVSHMEM: cannot get interrupt %d\n", pdev->irq);
printk(KERN_INFO"KVM_IVSHMEM: irq = %u regaddr = %x reg_size = %d\n",
pdev->irq,kvm_ivshmem_dev.regaddr, kvm_ivshmem_dev.reg_size);
}
} else {
printk(KERN_INFO "MSI-Xenabled\n");
}
return 0;
至此完成了pci設備的註冊和初始化,重點步驟的流程以下圖所示:
至此能夠查詢到pci設備
cat/proc/devices | grep kvm_ivshmem
而後建立設備節點:
mknod-mode=666 /dev/ivshmem c 245 0
Guest應用一端調用ioctl發起寫數據請求,另外一端ioctl返回準備讀數據,之間的通訊流程以下:
1、 Guest應用調用ivshmem_send->ioctl,操做/dev/ivshmem設備發起中斷,經過特權指令vmalunch陷入內核。
2、 內核ivshmem設備驅動kvm_ivshmem_ioctl調用write寫設備的bar0的doorbell寄存器,內核發現是kvm虛擬設備,因而vmexit到qemu處理
3、 qemu調用write驅動ivshmem_io_write函數,根據傳入地址,若是是寫doorbell調用,event_notifier_set(&s->peers[dest].eventfds[vector]),event_notifier_set實際往目標eventfds文件寫1。
4、 接收側qemu收到eventfd信號,調用ivshmem_receive–> ivshmem_IntrStatus_write 設置Status寄存器爲1,並經過hmem_update_irq>qemu_set_irq->kvm_set_irq->kvm_vm_ioctl實現中斷注入
5、 內核的虛擬的中斷控制器回調中斷處理函數kvm_ivshmem_interrupt,檢查bar0寄存器的值,根據相應含義中斷處理,觸發接收端所阻塞的kvm_ivshmem_ioctl 返回應用。
針對ivshmem的分析和調測資料有限,故將調測經過的方法也記錄下來:
Host os:
./qemu-system-x86_64 -hda img -L /pc-bios/ --smp 2 -deviceivshmem,shm=nahanni,size=32m -serial telnet:0.0.0.0:4001,server,nowait,nodelay-enable-kvm&
Guest os:
insmodkvm_ivshmem.ko
Guest os:
mknod-mode=666 /dev/ivshmem c 245 0
host os:
./grablocknahanni
Guest os:
./guestlock /dev/ivshmem
以上是guestos與hostos之間通訊,對於guestos與guestos之間通訊原理一致。
以上操做完畢,應用的發送和接收端分別打開ivshmem設備、並將該共享內存體mmap到各自的用戶空間後,便可經過文件讀寫實現共享內存通訊。
考慮到大數據量的數據傳輸,ivshmem開闢內存可能不夠一次性傳輸的狀況,須要考慮收發同步的流控問題。實現可參考tcp的實現方式,將ivshmem的共享內存分紅若干(好比16個)區域,內存第一個塊用做流控,具體來講:
在收發端使用full和empty參數來表示後面15個內存塊的讀寫狀況,empty表示有多少空餘塊能夠寫,當empty等於0則暫停寫操做,full表示有多少數據塊能夠讀,當full==0表示無數據可讀。同時各有一把pthread_spinlock_t鎖對這兩個變量進行保護,從而實現流量控制。
#defineCHUNK_SZ (1024)
#defineNEXT(i) ((i + 1) % 15)
#defineOFFSET(i) (i * CHUNK_SZ)
#defineFLOCK_LOC memptr
#defineFULL_LOC FLOCK_LOC +sizeof(pthread_spinlock_t)
#defineELOCK_LOC FULL_LOC + sizeof(int)
#defineEMPTY_LOC ELOCK_LOC + sizeof(pthread_spinlock_t)
#defineBUF_LOC (memptr + CHUNK_SZ)
Host os:
./ivshmem_server-m 64 -p /tmp/nahanni &
Host os:
./qemu-system-x86_64 -hda Img -L /pc-bios/ --smp 4 -chardevsocket,path=/tmp/nahanni,id=nahanni -deviceivshmem,chardev=nahanni,size=32m,msi=off -serialtelnet:0.0.0.0:4000,server,nowait,nodelay -enable-kvm&
./qemu-system-x86_64 -hda Img -L /pc-bios/ --smp 4 -chardevsocket,path=/tmp/nahanni,id=nahanni -deviceivshmem,chardev=nahanni,size=32m,msi=off -serial telnet:0.0.0.0:4001,server,nowait,nodelay-enable-kvm&
(/dev/shm/下如有nahanni同名文件需先刪除)
Guest os一、2:
insmodkvm_ivshmem.ko
Guest os一、2:
mknod-mode=666 /dev/ivshmem c 245 0
Guest os1:
./ftp_recv /dev/ivshmem ./wat 0
Guest os2:
./ftp_send /dev/ivshmem ./lht 1
X86虛擬機環境,虛擬機設置單核,虛擬機內存大小110M。
Ivshmem採用中斷模式,流控窗口大小16。
Virtio採用Bridge+vhost+vio最優化模式的。
Ivshmem調測步驟以下:
1、Host側啓動ivshmem_server,共享內存設置爲32M
./ivshmem_server-m 32 -p /tmp/nahanni &
2、host側qemu啓動:
./qemu-system-x86_64 -hda Img -L /pc-bios/ -chardevsocket,path=/tmp/nahanni,id=nahanni -deviceivshmem,chardev=nahanni,size=32m,msi=off -serialtelnet:0.0.0.0:4000,server,nowait,nodelay -enable-kvm&
接着端口號改成4001在啓動一個guestos。
3、分別telnet上127.0.0.1:4000和4001後插入內核模塊
insmodkvm_ivshmem.ko
4、分別建立節點
mknod-mode=666 /dev/ivshmem c 245 0
5、接收側guestos執行:
./ftp_recv /dev/ivshmem ./ recvfile 0 1024 1000
6、發送側guestos執行:
./ftp_send /dev/ivshmem ./sendfile 1 1024 1000
這裏的0、1表示vmid,1024爲數據塊大小,1000爲塊個數
Virtio調測步驟以下:
1、host側qemu啓動,使用vhost提高性能:
./qemu-system-x86_64-hda Img -L /pc-bios/ -balloon virtio -serial telnet:0.0.0.0:4002,server,nowait,nodelay-enable-kvm -net nic,macaddr=52:54:00:94:78:e9,model=virtio -nettap,script=no,vhost=on &
./qemu-system-x86_64-hda Img -L /pc-bios/ -balloon virtio -serialtelnet:0.0.0.0:4003,server,nowait,nodelay -enable-kvm -netnic,macaddr=52:52:00:54:68:e8,model=virtio -net tap,script=no,vhost=on &
2、host側網橋配置:
brctladdbr br0
brctladdif br0 tap0
brctladdif br0 tap1
brctladdif br0 eth0
ifconfigtap0 up
ifconfigtap1 up
ifconfigeth0 up
ifconfigbr0 10.74.154.3 up
ifconfigeth0 0.0.0.0
3、兩個guest側上分別運行
ifconfigeth0 10.74.154.4
ifconfigeth0 10.74.154.5
4、接收側guestos執行:
./tcp_server / recvfile 1024 1000
5、發送側guestos執行:
./tcp_client10.74.154.4 /sendfile 1024 1000
這裏的1024爲數據塊大小,1000爲塊個數
請聯繫www.lht@gmail.com,或回覆
測試結果以下表所示:
傳輸數據塊大小*塊個數 |
Virtio傳輸時間(ns) |
|
256*100 |
700 |
2000 |
256*1000 |
1200 |
6000 |
256*10000 |
7000 |
37000 |
4096*100 |
1200 |
7500 |
4096*1000 |
2600 |
38000 |
4096*10000 |
20000 |
380000 |
65536*100 |
3000 |
50000 |
65536*1000 |
30000 |
500000 |
須要指出的是,非中斷模式測試結果與中斷模式性能一致,這裏沒有單列性能對比數據。
從以上測試數據能夠獲得以下結論:
一、 ivshmem在傳輸1m以上數據時,性能基本穩定,數據量與時間基本成線性關係
二、 1m以上數據傳輸性能,ivshmem性能是virtio性能10~20倍
三、小數據量傳輸ivshmem性能比virtio有明顯優點。
原文連接: http://blog.csdn.net/u014358116/article/details/22753423
做者:愛海tatao [ 轉載請保留原文出處、做者和連接。]