__user && address_space(1)

所有的内核代码,基本都包含了linux/compile.h这个文件,所以它是基础,打算先分析这个文件里的代码看看,有空再分析分析其它的代码。

首先印入眼帘的是对__ASSEMBLY__这个宏的判断,这个变量实际是在编译汇编代码的时候,由编译器使用-D这样的参数加进去的,AFLAGS这个变量也定义了这个变量,gcc会把这个宏定义为1。用在这里,是因为汇编代码里,不会用到类似于__user这样的属性(关于__user这样的属性是怎么回子事,本文后面会提到),因为这样的属性是在定义函数的时候加的,这样避免不必要的在编译汇编代码时候的引用。
接下来是一个对__CHECKER__这个宏的判断,这里需要讲的东西比较多。
当编译内核代码的时候,使用make C=1或C=2的时候,会调用一个叫Sparse的工具,这个工具对内核代码进行检查,怎么检查呢,就是靠对那些声明过Sparse这个工具所能识别的特性的内核函数或是变量进行检查。在调用Sparse这个工具的同时,在Sparse代码里,会加上#define __CHECKER__ 1的字样。换句话说,就是,如果使用Sparse对代码进行检查,那么内核代码就会定义__CHECKER__宏,否则就不定义。
所以这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
那么这些个属性是干什么用的呢,我一个个做介绍。
这样的属性说明,有一部分在gcc的文档里还没有加进去,至少我在gcc 4.3.2的特性里没有看到,网上有哥们问类似的问题,Greg对他进行了解答,然后他对Greg抱怨文档的事,Greg对他说,他有时间抱怨的话,还不如自己来更新文档。他不能对一个免费工具的文档有如此之高的要求,除非他付费。

# define __user  __attribute__((noderef, address_space(1)))

__user这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1,即用户程序空间的。
这里把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。

所以在内核函数里,有一个copy_to_user的函数,函数的参数定义就使用了这种方式。当然,这种特性检查,只有当机器上安装了Sparse这个工具,而且进行了编译的时候调用,才能起作用的。

# define __kernel /* default address space */

根据定义,就是默认的地址空间,即0,我想定义成__attribute__((noderef, address_space(0)))也是没有问题的。

# define __safe  __attribute__((safe))

这个定义在sparse里也有,内核代码是在2.6.6-rc1版本变到2.6.6-rc2的时候被Linus加入的,经过我的艰苦的查找,终于查找到原因了,知道了为什么Linus要加入这个定义,原因是这样的:
有人发现在代码编译的时候,编译器对变量的检查有些苛刻,导致代码在编译的时候老是出问题(我这里没有去检查是编译不通过还是有警告信息,因为现在的编译器已经不是当年的编译器了,代码也不是当年的代码)。比如说这样一个例子,
 int test( struct a * a, struct b * b, struct c * c ) {
  return a->a + b->b + c->c;
 }
这个编译的时候会有问题,因为没有检查参数是否为空,就直接进行调用。但是呢,在内核里,有好多函数,当它们被调用的时候,这些个参数必定不为空,所以根本用不着去对这些个参数进行非空的检查,所以呢,就增加了一个__safe的属性,如果这样声明变量,
 int test( struct a * __safe a, struct b * __safe b, struct c * __safe c ) {
  return a->a + b->b + c->c;
 }
编译就没有问题了。

不过我在现在的代码里没有发现有使用__safe这个定义的地方,不知道是不是编译器现在已经支持这种特殊的情况了,所以就不用再加这样的代码了。

# define __force __attribute__((force))

表示所定义的变量类型是可以做强制类型转换的,在进行Sparse分析的时候,是不用报告警信息的。

# define __nocast __attribute__((nocast))

这里表示这个变量的参数类型与实际参数类型一定得对得上才行,要不就在Sparse的时候生产告警信息。

# define __iomem __attribute__((noderef, address_space(2)))

这个定义与__user, __user是一样的,只不过这里的变量地址是需要在设备地址映射空间的。

# define __acquires(x) __attribute__((context(x,0,1)))
# define __releases(x) __attribute__((context(x,1,0)))

这是一对相互关联的函数定义,第一句表示参数x在执行之前,引用计数必须为0,执行后,引用计数必须为1,第二句则正好相反,这个定义是用在修饰函数定义的变量的。

# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)

这是一对相互关联的函数定义,第一句表示要增加变量x的计数,增加量为1,第二句则正好相反,这个是用来函数执行的过程中。

以上四句如果在代码中出现了不平衡的状况,那么在Sparse的检测中就会报警。当然,Sparse的检测只是一个手段,而且是静态检查代码的手段,所以它的帮助有限,有可能把正确的认为是错误的而发出告警。要是对以上四句的意思还是不太了解的话,请在源代码里搜一下相关符号的用法就能知道了。这第一组与第二组,在本质上,是没什么区别的,只是使用的位置上,有所区别罢了。

# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)

这句话的意思就是条件锁。当c这个值不为0时,则让计数值加1,并返回值为1。不过这里我有一个疑问,就是在这里,有一个__cond_lock定义,但没有定义相应的__cond_unlock,那么在变量的释放上,就没办法做到一致。而且我查了一下关于spin_trylock()这个函数的定义,它就用了__cond_lock,而且里面又用了_spin_trylock函数,在_spin_trylock函数里,再经过几次调用,就会使用到__acquire函数,这样的话,相当于一个操作,就进行了两次计算,会导致Sparse的检测出现告警信息,经过我写代码进行实验,验证了我的判断,确实是会出现告警信息,如果我写两遍unlock指令,就没有告警信息了,但这是与程序的运行是不一致的。

extern void __chk_user_ptr(const volatile void __user *);
extern void __chk_io_ptr(const volatile void __iomem *);

这两句比较有意思。这里只是定义了函数,但是代码里没有函数的实现。这样做的目的,就是在进行Sparse的时候,让Sparse给代码做必要的参数类型检查,在实际的编译过程中,并不需要这两个函数的实现。

#define notrace __attribute__((no_instrument_function))

这一句,是定义了一个属性,这个属性可以用来修饰一个函数,指定这个函数不被跟踪。那么这个属性到底是怎么回子事呢?原来,在gcc编译器里面,实现了一个非常强大的功能,如果在编译的时候把一个相应的选择项打开,那么就可以在执行完程序的时候,用一些工具来显示整个函数被调用的过程,这样就不需要让程序员手动在所有的函数里一点点添加能显示函数被调用过程的语句,这样耗时耗力,还容易出错。那么对应在应用程序方面,可以使用Graphviz这个工具来进行显示,至于使用说明与软件实现的原理可以自己在网上查一查,很容易查到。对应于内核,因为内核一直是在运行阶段,所以就不能使用这套东西了,内核是在自己的内部实现了一个ftrace的机制,编译内核的时候,如果打开这个选项,那么通过挂载一个debugfs的文件系统来进行相应内容的显示,具体的操作步骤,可以参看内核源码所带的文档。那上面说了这么多,与notrace这个属性有什么关系呢?因为在进行函数调用流程的显示过程中,是使用了两个特殊的函数的,当函数被调用与函数被执行完返回之前,都会分别调用这两个特别的函数。如果不把这两个函数的函数指定为不被跟踪的属性,那么整个跟踪的过程就会陷入一个无限循环当中。

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

这两句是一对对应关系。__builtin_expect(expr, c)这个函数是新版gcc支持的,它是用来作代码优化的,用来告诉编译器,expr的期,非常有可能是c,这样在gcc生成对应的汇编代码的时候,会把相应的可能执行的代码都放在一起,这样能少执行代码的跳转。为什么这样能提高CPU的执行效率呢?因为CPU在执行的时候,都是有预先取指令的机制的,把将要执行的指令取出一部分出来准备执行。CPU不知道程序的逻辑,所以都是从可程序程序里挨着取的,如果这个时候,能不做跳转,则CPU预先取出的指令都可以接着使用,反之,则预先取出来的指令都是没有用的。还有个问题是需要注意的,在__builtin_expect的定义中,以前的版本是没有!!这个符号的,这个符号的作用其实就是负负得正,为什么要这样做呢?就是为了保证非零的x的值,后来都为1,如果为零的0值,后来都为0,仅此而已。

#ifndef barrier
# define barrier() __memory_barrier()
#endif

这里表示如果没有定义barrier函数,则定义barrier()函数为__memory_barrier()。但在内核代码里,是会包含compiler-gcc.h这个文件的,所以在这个文件里,定义barrier()为__asm__ __volatile__("": : :"memory")。barrier翻译成中文就是屏障的意思,在这里,为什么要一个屏障呢?这是因为CPU在执行的过程中,为了优化指令,可能会对部分指令以它自己认为最优的方式进行执行,这个执行的顺序并不一定是按照程序在源码内写的顺序。编译器也有可能在生成二进制指令的时候,也进行一些优化。这样就有可能在多CPU,多线程或是互斥锁的执行中遇到问题。那么这个内存屏障可以看作是一条线,内存屏障用在这里,就是为了保证屏障以上的操作,不会影响到屏障以下的操作。然后再看看这个屏障怎么实现的。__asm__表示后面的东西都是汇编指令,当然,这是一种在C语言中嵌入汇编的方法,语法有其特殊性,我在这里只讲跟这条指令有关的。__volatile__表示不对此处的汇编指令做优化,这样就会保证这里代码的正确性。""表示这里是个空指令,那么既然是空指令,则所对应的指令所需要的输入与输出都没有。在gcc中规定,如果以这种方式嵌入汇编,如果输出没有,则需要两个冒号来代替输出操作数的位置,所以需要加两个::,这时的指令就为"" : :。然后再加上为分隔输入而加入的冒号,再加上空的输入,即为"" : : :。后面的memory是gcc中的一个特殊的语法,加上它,gcc编译器则会产生一个动作,这个动作使gcc不保留在寄存器内内存的值,并且对相应的内存不会做存储与加载的优化处理,这个动作不产生额外的代码,这个行为是由gcc编译器来保证完成的。如果对这部分有更大的兴趣,可以考察gcc的帮助文档与内核中一篇名为memory-barriers.txt的文章。

#ifndef RELOC_HIDE
# define RELOC_HIDE(ptr, off)     /
  ({ unsigned long __ptr;     /
     __ptr = (unsigned long) (ptr);    /
    (typeof(ptr)) (__ptr + (off)); })
#endif

这个没有什么太多值得讲的,也能看明白,虽然不知道具体用在哪里,所以留做以后遇到了再说吧。

接下来好多定义都没有实现,可以看一看注释就知道了,所以这里就不多说了。唉,不过再插一句,__deprecated属性的实现是为deprecated。

#define noinline_for_stack noinline

#ifndef __always_inline
#define __always_inline inline
#endif

这里noinline与inline属性是两个对立的属性,从词面的意思就非常好理解了。

#ifndef __cold
#define __cold
#endif

从注释中就可以看出来,如果一个函数的属性为__cold,那么编译器就会认为这个函数几乎是不可能被调用的,在进行代码优化的时候,就会考虑到这一点。不过我没有看到在gcc里支持这个属性的说明。

#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif

这个比较容易理解了,用来修饰一个函数是放在哪个区域里的,不使用编译器默认的方式。这个区域的名字由定义者自己取,格式就是__section__加上用户输入的参数。

#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

这个函数的定义很有意思,它就是访问这个x参数所对应的东西一次,它是这样做的:先取得这个x的地址,然后把这个地址进行变换,转换成一个指向这个地址类型的指针,然后再取得这个指针所指向的内容。这样就达到了访问一次的目的,哈哈。

真不容易,终于把这个东西写完了,仅仅几十行的代码,里面所包含的知识真的是异常丰富,通过分析这个头文件,我自己学得了不少东西,不敢独享,拿出来给与兴趣的朋友一同分享。

/// Client for a vhost-user device. The API is a thin abstraction over the vhost-user protocol. pub struct BackendClient { connection: Connection<FrontendReq>, // Cached virtio features from the backend. virtio_features: u64, // Cached acked virtio features from the driver. acked_virtio_features: u64, // Cached vhost-user protocol features. acked_protocol_features: u64, } impl BackendClient { /// Create a new instance. pub fn new(connection: Connection<FrontendReq>) -> Self { BackendClient { connection, virtio_features: 0, acked_virtio_features: 0, acked_protocol_features: 0, } } /// Get a bitmask of supported virtio/vhost features. pub fn get_features(&mut self) -> Result<u64> { let hdr = self.send_request_header(FrontendReq::GET_FEATURES, None)?; let val = self.recv_reply::<VhostUserU64>(&hdr)?; self.virtio_features = val.value; Ok(self.virtio_features) } /// Inform the vhost subsystem which features to enable. /// This should be a subset of supported features from get_features(). pub fn set_features(&mut self, features: u64) -> Result<()> { let val = VhostUserU64::new(features); let hdr = self.send_request_with_body(FrontendReq::SET_FEATURES, &val, None)?; self.acked_virtio_features = features & self.virtio_features; self.wait_for_ack(&hdr) } /// Set the current process as the owner of the vhost backend. /// This must be run before any other vhost commands. pub fn set_owner(&self) -> Result<()> { let hdr = self.send_request_header(FrontendReq::SET_OWNER, None)?; self.wait_for_ack(&hdr) } /// Used to be sent to request disabling all rings /// This is no longer used. pub fn reset_owner(&self) -> Result<()> { let hdr = self.send_request_header(FrontendReq::RESET_OWNER, None)?; self.wait_for_ack(&hdr) } /// Set the memory map regions on the backend so it can translate the vring /// addresses. In the ancillary data there is an array of file descriptors pub fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> { if regions.is_empty() || regions.len() > MAX_ATTACHED_FD_ENTRIES { return Err(VhostUserError::InvalidParam( "set_mem_table: regions empty or exceed max allowed regions per req.", )); } let mut ctx = VhostUserMemoryContext::new(); for region in regions.iter() { if region.memory_size == 0 || region.mmap_handle == INVALID_DESCRIPTOR { return Err(VhostUserError::InvalidParam( "set_mem_table: invalid memory region", )); } let reg = VhostUserMemoryRegion { guest_phys_addr: region.guest_phys_addr, memory_size: region.memory_size, user_addr: region.userspace_addr, mmap_offset: region.mmap_offset, }; ctx.append(&reg, region.mmap_handle); } let body = VhostUserMemory::new(ctx.regions.len() as u32); let hdr = self.send_request_with_payload( FrontendReq::SET_MEM_TABLE, &body, ctx.regions.as_bytes(), Some(ctx.fds.as_slice()), )?; self.wait_for_ack(&hdr) } /// Set base address for page modification logging. pub fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> { let val = VhostUserU64::new(base); let should_have_fd = self.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0; if should_have_fd != fd.is_some() { return Err(VhostUserError::InvalidParam("set_log_base: FD is missing")); } let _ = self.send_request_with_body( FrontendReq::SET_LOG_BASE, &val, fd.as_ref().map(std::slice::from_ref), )?; Ok(()) } /// Specify an event file descriptor to signal on log write. pub fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> { let fds = [fd]; let hdr = self.send_request_header(FrontendReq::SET_LOG_FD, Some(&fds))?; self.wait_for_ack(&hdr) } /// Set the number of descriptors in the vring. pub fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> { let val = VhostUserVringState::new(queue_index as u32, num.into()); let hdr = self.send_request_with_body(FrontendReq::SET_VRING_NUM, &val, None)?; self.wait_for_ack(&hdr) } /// Set the addresses for a given vring. pub fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> { if config_data.flags & !(VhostUserVringAddrFlags::all().bits()) != 0 { return Err(VhostUserError::InvalidParam( "set_vring_addr: unsupported vring flags", )); } let val = VhostUserVringAddr::from_config_data(queue_index as u32, config_data); let hdr = self.send_request_with_body(FrontendReq::SET_VRING_ADDR, &val, None)?; self.wait_for_ack(&hdr) } /// Set the first index to look for available descriptors. // TODO: b/331466964 - Arguments and message format are wrong for packed queues. pub fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> { let val = VhostUserVringState::new(queue_index as u32, base.into()); let hdr = self.send_request_with_body(FrontendReq::SET_VRING_BASE, &val, None)?; self.wait_for_ack(&hdr) } /// Get the available vring base offset. // TODO: b/331466964 - Return type is wrong for packed queues. pub fn get_vring_base(&self, queue_index: usize) -> Result<u32> { let req = VhostUserVringState::new(queue_index as u32, 0); let hdr = self.send_request_with_body(FrontendReq::GET_VRING_BASE, &req, None)?; let reply = self.recv_reply::<VhostUserVringState>(&hdr)?; Ok(reply.num) } /// Set the event to trigger when buffers have been used by the host. /// /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag /// is set when there is no file descriptor in the ancillary data. This signals that polling /// will be used instead of waiting for the call. pub fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> { let hdr = self.send_fd_for_vring( FrontendReq::SET_VRING_CALL, queue_index, event.as_raw_descriptor(), )?; self.wait_for_ack(&hdr) } /// Set the event that will be signaled by the guest when buffers are available for the host to /// process. /// /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag /// is set when there is no file descriptor in the ancillary data. This signals that polling /// should be used instead of waiting for a kick. pub fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> { let hdr = self.send_fd_for_vring( FrontendReq::SET_VRING_KICK, queue_index, event.as_raw_descriptor(), )?; self.wait_for_ack(&hdr) } /// Set the event that will be signaled by the guest when error happens. /// /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag /// is set when there is no file descriptor in the ancillary data. pub fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> { let hdr = self.send_fd_for_vring( FrontendReq::SET_VRING_ERR, queue_index, event.as_raw_descriptor(), )?; self.wait_for_ack(&hdr) } /// Front-end and back-end negotiate a channel over which to transfer the back-end’s internal /// state during migration. /// /// Requires VHOST_USER_PROTOCOL_F_DEVICE_STATE to be negotiated. pub fn set_device_state_fd( &self, transfer_direction: VhostUserTransferDirection, migration_phase: VhostUserMigrationPhase, fd: &impl AsRawDescriptor, ) -> Result<Option<File>> { if self.acked_protocol_features & VhostUserProtocolFeatures::DEVICE_STATE.bits() == 0 { return Err(VhostUserError::InvalidOperation); } // Send request. let req = DeviceStateTransferParameters { transfer_direction: match transfer_direction { VhostUserTransferDirection::Save => 0, VhostUserTransferDirection::Load => 1, }, migration_phase: match migration_phase { VhostUserMigrationPhase::Stopped => 0, }, }; let hdr = self.send_request_with_body( FrontendReq::SET_DEVICE_STATE_FD, &req, Some(&[fd.as_raw_descriptor()]), )?; // Receive reply. let (reply, files) = self.recv_reply_with_files::<VhostUserU64>(&hdr)?; let has_err = reply.value & 0xff != 0; let invalid_fd = reply.value & 0x100 != 0; if has_err { return Err(VhostUserError::BackendInternalError); } match (invalid_fd, files.len()) { (true, 0) => Ok(None), (false, 1) => Ok(files.into_iter().next()), _ => Err(VhostUserError::IncorrectFds), } } /// After transferring the back-end’s internal state during migration, check whether the /// back-end was able to successfully fully process the state. pub fn check_device_state(&self) -> Result<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::DEVICE_STATE.bits() == 0 { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_header(FrontendReq::CHECK_DEVICE_STATE, None)?; let reply = self.recv_reply::<VhostUserU64>(&hdr)?; if reply.value != 0 { return Err(VhostUserError::BackendInternalError); } Ok(()) } /// Get the protocol feature bitmask from the underlying vhost implementation. pub fn get_protocol_features(&self) -> Result<VhostUserProtocolFeatures> { if self.virtio_features & 1 << VHOST_USER_F_PROTOCOL_FEATURES == 0 { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_header(FrontendReq::GET_PROTOCOL_FEATURES, None)?; let val = self.recv_reply::<VhostUserU64>(&hdr)?; Ok(VhostUserProtocolFeatures::from_bits_truncate(val.value)) } /// Enable protocol features in the underlying vhost implementation. pub fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()> { if self.virtio_features & 1 << VHOST_USER_F_PROTOCOL_FEATURES == 0 { return Err(VhostUserError::InvalidOperation); } if features.contains(VhostUserProtocolFeatures::SHARED_MEMORY_REGIONS) && !features.contains(VhostUserProtocolFeatures::BACKEND_REQ) { return Err(VhostUserError::FeatureMismatch); } let val = VhostUserU64::new(features.bits()); let hdr = self.send_request_with_body(FrontendReq::SET_PROTOCOL_FEATURES, &val, None)?; // Don't wait for ACK here because the protocol feature negotiation process hasn't been // completed yet. self.acked_protocol_features = features.bits(); self.wait_for_ack(&hdr) } /// Query how many queues the backend supports. pub fn get_queue_num(&self) -> Result<u64> { if !self.is_feature_mq_available() { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_header(FrontendReq::GET_QUEUE_NUM, None)?; let val = self.recv_reply::<VhostUserU64>(&hdr)?; if val.value > VHOST_USER_MAX_VRINGS { return Err(VhostUserError::InvalidMessage); } Ok(val.value) } /// Signal backend to enable or disable corresponding vring. /// /// Backend must not pass data to/from the ring until ring is enabled by /// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has been /// disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0. pub fn set_vring_enable(&self, queue_index: usize, enable: bool) -> Result<()> { // set_vring_enable() is supported only when PROTOCOL_FEATURES has been enabled. if self.acked_virtio_features & 1 << VHOST_USER_F_PROTOCOL_FEATURES == 0 { return Err(VhostUserError::InvalidOperation); } let val = VhostUserVringState::new(queue_index as u32, enable.into()); let hdr = self.send_request_with_body(FrontendReq::SET_VRING_ENABLE, &val, None)?; self.wait_for_ack(&hdr) } /// Fetch the contents of the virtio device configuration space. pub fn get_config( &self, offset: u32, size: u32, flags: VhostUserConfigFlags, buf: &[u8], ) -> Result<(VhostUserConfig, VhostUserConfigPayload)> { let body = VhostUserConfig::new(offset, size, flags); if !body.is_valid() { return Err(VhostUserError::InvalidParam( "get_config: VhostUserConfig is invalid", )); } // depends on VhostUserProtocolFeatures::CONFIG if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { return Err(VhostUserError::InvalidOperation); } // vhost-user spec states that: // "Request payload: virtio device config space" // "Reply payload: virtio device config space" let hdr = self.send_request_with_payload(FrontendReq::GET_CONFIG, &body, buf, None)?; let (body_reply, buf_reply, rfds) = self.recv_reply_with_payload::<VhostUserConfig>(&hdr)?; if !rfds.is_empty() { return Err(VhostUserError::InvalidMessage); } else if body_reply.size == 0 { return Err(VhostUserError::BackendInternalError); } else if body_reply.size != body.size || body_reply.size as usize != buf.len() || body_reply.offset != body.offset { return Err(VhostUserError::InvalidMessage); } Ok((body_reply, buf_reply)) } /// Change the virtio device configuration space. It also can be used for live migration on the /// destination host to set readonly configuration space fields. pub fn set_config(&self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> { let body = VhostUserConfig::new( offset, buf.len() .try_into() .map_err(VhostUserError::InvalidCastToInt)?, flags, ); if !body.is_valid() { return Err(VhostUserError::InvalidParam( "set_config: VhostUserConfig is invalid", )); } // depends on VhostUserProtocolFeatures::CONFIG if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_with_payload(FrontendReq::SET_CONFIG, &body, buf, None)?; self.wait_for_ack(&hdr) } /// Setup backend communication channel. pub fn set_backend_req_fd(&self, fd: &dyn AsRawDescriptor) -> Result<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::BACKEND_REQ.bits() == 0 { return Err(VhostUserError::InvalidOperation); } let fds = [fd.as_raw_descriptor()]; let hdr = self.send_request_header(FrontendReq::SET_BACKEND_REQ_FD, Some(&fds))?; self.wait_for_ack(&hdr) } /// Retrieve shared buffer for inflight I/O tracking. pub fn get_inflight_fd( &self, inflight: &VhostUserInflight, ) -> Result<(VhostUserInflight, File)> { if self.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits() == 0 { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_with_body(FrontendReq::GET_INFLIGHT_FD, inflight, None)?; let (inflight, files) = self.recv_reply_with_files::<VhostUserInflight>(&hdr)?; match into_single_file(files) { Some(file) => Ok((inflight, file)), None => Err(VhostUserError::IncorrectFds), } } /// Set shared buffer for inflight I/O tracking. pub fn set_inflight_fd(&self, inflight: &VhostUserInflight, fd: RawDescriptor) -> Result<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits() == 0 { return Err(VhostUserError::InvalidOperation); } if inflight.mmap_size == 0 || inflight.num_queues == 0 || inflight.queue_size == 0 || fd == INVALID_DESCRIPTOR { return Err(VhostUserError::InvalidParam( "set_inflight_fd: invalid fd or params", )); } let hdr = self.send_request_with_body(FrontendReq::SET_INFLIGHT_FD, inflight, Some(&[fd]))?; self.wait_for_ack(&hdr) } /// Query the maximum amount of memory slots supported by the backend. pub fn get_max_mem_slots(&self) -> Result<u64> { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0 { return Err(VhostUserError::InvalidOperation); } let hdr = self.send_request_header(FrontendReq::GET_MAX_MEM_SLOTS, None)?; let val = self.recv_reply::<VhostUserU64>(&hdr)?; Ok(val.value) } /// Add a new guest memory mapping for vhost to use. pub fn add_mem_region(&self, region: &VhostUserMemoryRegionInfo) -> Result<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0 { return Err(VhostUserError::InvalidOperation); } if region.memory_size == 0 || region.mmap_handle == INVALID_DESCRIPTOR { return Err(VhostUserError::InvalidParam( "add_mem_region: region empty or mmap handle invalid", )); } let body = VhostUserSingleMemoryRegion::new( region.guest_phys_addr, region.memory_size, region.userspace_addr, region.mmap_offset, ); let fds = [region.mmap_handle]; let hdr = self.send_request_with_body(FrontendReq::ADD_MEM_REG, &body, Some(&fds))?; self.wait_for_ack(&hdr) } /// Remove a guest memory mapping from vhost. pub fn remove_mem_region(&self, region: &VhostUserMemoryRegionInfo) -> Result<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0 { return Err(VhostUserError::InvalidOperation); } if region.memory_size == 0 { return Err(VhostUserError::InvalidParam( "remove_mem_region: cannot remove zero sized region", )); } let body = VhostUserSingleMemoryRegion::new( region.guest_phys_addr, region.memory_size, region.userspace_addr, region.mmap_offset, ); let hdr = self.send_request_with_body(FrontendReq::REM_MEM_REG, &body, None)?; self.wait_for_ack(&hdr) } /// Gets the shared memory regions used by the device. pub fn get_shared_memory_regions(&self) -> Result<Vec<VhostSharedMemoryRegion>> { let hdr = self.send_request_header(FrontendReq::GET_SHARED_MEMORY_REGIONS, None)?; let (body_reply, buf_reply, rfds) = self.recv_reply_with_payload::<VhostUserU64>(&hdr)?; let struct_size = mem::size_of::<VhostSharedMemoryRegion>(); if !rfds.is_empty() || buf_reply.len() != body_reply.value as usize * struct_size { return Err(VhostUserError::InvalidMessage); } let mut regions = Vec::new(); let mut offset = 0; for _ in 0..body_reply.value { regions.push( // Can't fail because the input is the correct size. VhostSharedMemoryRegion::read_from(&buf_reply[offset..(offset + struct_size)]) .unwrap(), ); offset += struct_size; } Ok(regions) } fn send_request_header( &self, code: FrontendReq, fds: Option<&[RawDescriptor]>, ) -> VhostUserResult<VhostUserMsgHeader<FrontendReq>> { let hdr = self.new_request_header(code, 0); self.connection.send_header_only_message(&hdr, fds)?; Ok(hdr) } fn send_request_with_body<T: Sized + AsBytes>( &self, code: FrontendReq, msg: &T, fds: Option<&[RawDescriptor]>, ) -> VhostUserResult<VhostUserMsgHeader<FrontendReq>> { let hdr = self.new_request_header(code, mem::size_of::<T>() as u32); self.connection.send_message(&hdr, msg, fds)?; Ok(hdr) } fn send_request_with_payload<T: Sized + AsBytes>( &self, code: FrontendReq, msg: &T, payload: &[u8], fds: Option<&[RawDescriptor]>, ) -> VhostUserResult<VhostUserMsgHeader<FrontendReq>> { if let Some(fd_arr) = fds { if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES { return Err(VhostUserError::InvalidParam( "send_request_with_payload: too many FDs supplied with message", )); } } let len = mem::size_of::<T>() .checked_add(payload.len()) .ok_or(VhostUserError::OversizedMsg)?; let hdr = self.new_request_header( code, len.try_into().map_err(VhostUserError::InvalidCastToInt)?, ); self.connection .send_message_with_payload(&hdr, msg, payload, fds)?; Ok(hdr) } fn send_fd_for_vring( &self, code: FrontendReq, queue_index: usize, fd: RawDescriptor, ) -> VhostUserResult<VhostUserMsgHeader<FrontendReq>> { // Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. // This flag is set when there is no file descriptor in the ancillary data. This signals // that polling will be used instead of waiting for the call. let msg = VhostUserU64::new(queue_index as u64); let hdr = self.new_request_header(code, mem::size_of::<VhostUserU64>() as u32); self.connection.send_message(&hdr, &msg, Some(&[fd]))?; Ok(hdr) } fn recv_reply<T: Sized + FromBytes + AsBytes + Default + VhostUserMsgValidator>( &self, hdr: &VhostUserMsgHeader<FrontendReq>, ) -> VhostUserResult<T> { if hdr.is_reply() { return Err(VhostUserError::InvalidParam( "recv_reply: header is not a reply", )); } let (reply, body, rfds) = self.connection.recv_message::<T>()?; if !reply.is_reply_for(hdr) || !rfds.is_empty() || !body.is_valid() { return Err(VhostUserError::InvalidMessage); } Ok(body) } fn recv_reply_with_files<T: Sized + AsBytes + FromBytes + Default + VhostUserMsgValidator>( &self, hdr: &VhostUserMsgHeader<FrontendReq>, ) -> VhostUserResult<(T, Vec<File>)> { if hdr.is_reply() { return Err(VhostUserError::InvalidParam( "with_files: expected a reply, but the header is not marked as a reply", )); } let (reply, body, files) = self.connection.recv_message::<T>()?; if !reply.is_reply_for(hdr) || !body.is_valid() { return Err(VhostUserError::InvalidMessage); } Ok((body, files)) } fn recv_reply_with_payload<T: Sized + AsBytes + FromBytes + Default + VhostUserMsgValidator>( &self, hdr: &VhostUserMsgHeader<FrontendReq>, ) -> VhostUserResult<(T, Vec<u8>, Vec<File>)> { if hdr.is_reply() { return Err(VhostUserError::InvalidParam( "with_payload: expected a reply, but the header is not marked as a reply", )); } let (reply, body, buf, files) = self.connection.recv_message_with_payload::<T>()?; if !reply.is_reply_for(hdr) || !files.is_empty() || !body.is_valid() { return Err(VhostUserError::InvalidMessage); } Ok((body, buf, files)) } fn wait_for_ack(&self, hdr: &VhostUserMsgHeader<FrontendReq>) -> VhostUserResult<()> { if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() == 0 || !hdr.is_need_reply() { return Ok(()); } let (reply, body, rfds) = self.connection.recv_message::<VhostUserU64>()?; if !reply.is_reply_for(hdr) || !rfds.is_empty() || !body.is_valid() { return Err(VhostUserError::InvalidMessage); } if body.value != 0 { return Err(VhostUserError::BackendInternalError); } Ok(()) } fn is_feature_mq_available(&self) -> bool { self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0 } #[inline] fn new_request_header( &self, request: FrontendReq, size: u32, ) -> VhostUserMsgHeader<FrontendReq> { VhostUserMsgHeader::new(request, 0x1, size) } }
最新发布
05-30
<think>好的,我现在需要仔细分析用户提供的Rust代码,并生成相应的解释和相关问题。首先,这段代码实现了一个vhost-user协议的客户端后端,负责与vhost-user后端服务进行通信。用户希望我逐行解释这个结构体的实现,并给出相关问题。 首先,我应该先通读代码,理解各个方法的作用。结构体BackendClient包含连接、特性缓存等字段。关键方法包括新建实例、获取特性、设置特性、设置内存表、配置虚拟队列等。接下来,我需要确认每个方法的功能。例如,get_features方法用于获取后端支持的特性,set_features用于设置协商后的特性。set_mem_table方法设置内存映射,这对虚拟化设备非常重要,因为它允许后端访问客户机的内存区域。 然后,注意到代码中有许多与协议特性相关的检查,比如在set_protocol_features中检查是否支持协议特性,并处理错误。这需要解释这些检查的必要性以及它们如何确保协议的正确协商。 在处理消息发送和接收的部分,如send_request_with_payload和recv_reply,需要说明它们如何封装vhost-user协议的消息格式,包括头部、有效载荷和文件描述符的传递。同时,错误处理机制如wait_for_ack的作用也需要解释。此外,代码中涉及的文件描述符传递(如set_vring_call、set_vring_kick)和共享内存区域管理(如get_shared_memory_regions)是vhost-user协议的关键部分,需要详细说明其工作原理和实现方式。 在解释过程中,需要注意Rust特有的语法和模式,如AsRawDescriptor用于处理原始文件描述符,anyhow用于错误处理,以及使用Mutex进行线程安全操作。同时,代码中的条件检查和错误返回也是重点,需要说明它们如何确保代码的健壮性。 最后,生成相关问题时要覆盖vhost-user协议的不同方面,包括特性协商、内存管理、消息传递机制和错误处理等,确保问题具有针对性和深度,能够引导进一步学习。</think>以下是`BackendClient`结构体的详细解析: ```rust /// vhost-user前端客户端,实现协议通信核心逻辑 pub struct BackendClient { connection: Connection<FrontendReq>, // 协议通信通道 virtio_features: u64, // 后端支持的virtio特性位掩码 acked_virtio_features: u64, // 已确认启用的virtio特性 acked_protocol_features: u64, // 已确认的vhost-user协议特性 } impl BackendClient { /// 发送内存映射表(核心方法) pub fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> { let mut ctx = VhostUserMemoryContext::new(); for region in regions { // 验证内存区域有效性 if region.memory_size == 0 || region.mmap_handle == INVALID_DESCRIPTOR { return Err(InvalidParam("invalid memory region")); } // 构建内存区域描述 let reg = VhostUserMemoryRegion { guest_phys_addr: region.guest_phys_addr, memory_size: region.memory_size, user_addr: region.userspace_addr, mmap_offset: region.mmap_offset, }; ctx.append(&reg, region.mmap_handle); // 添加文件描述符 } // 发送协议消息 let body = VhostUserMemory::new(ctx.regions.len() as u32); self.send_request_with_payload( FrontendReq::SET_MEM_TABLE, &body, ctx.regions.as_bytes(), Some(ctx.fds.as_slice()), )?; self.wait_for_ack(&hdr) } /// 配置虚拟队列地址(关键操作) pub fn set_vring_addr(&self, queue_index: usize, config: &VringConfigData) -> Result<()> { // 验证标志位有效性 if config.flags & !VhostUserVringAddrFlags::all().bits() != 0 { return Err(InvalidParam("unsupported flags")); } // 构建地址描述结构体 let addr = VhostUserVringAddr::from_config_data(queue_index as u32, config); self.send_request_with_body(FrontendReq::SET_VRING_ADDR, &addr, None)?; self.wait_for_ack(&hdr) } /// 协议特性协商流程 pub fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()> { // 前置条件检查 if self.virtio_features & VHOST_USER_F_PROTOCOL_FEATURES == 0 { return Err(InvalidOperation); } // 特性依赖检查 if features.contains(SHARED_MEMORY_REGIONS) && !features.contains(BACKEND_REQ) { return Err(FeatureMismatch); } // 发送协议特性 let val = VhostUserU64::new(features.bits()); self.send_request_with_body(FrontendReq::SET_PROTOCOL_FEATURES, &val, None)?; self.acked_protocol_features = features.bits(); Ok(()) } } ``` **核心机制解析**: 1. 内存映射传输机制: ```rust ctx.append(&reg, region.mmap_handle); ``` - 每个内存区域通过`mmap_handle`传递文件描述符 - 使用`VhostUserMemoryContext`封装内存布局信息 - 通过SCM_RIGHTS机制传递共享内存FD 2. 虚拟队列配置流程: ```rust VhostUserVringAddr { flags: config.flags, descriptor: config.desc_table, used: config.used_ring, available: config.avail_ring, log: config.log, } ``` - 配置描述符表地址、可用环地址等关键指针 - 支持MSI-X中断与传统中断回退机制 3. 协议状态机管理: ```rust fn wait_for_ack(&self, hdr: &Header) -> Result<()> { if self.acked_protocol_features & REPLY_ACK == 0 { return Ok(()); } let (reply, body) = self.recv_reply()?; if body.value != 0 { return Err(BackendInternalError); } Ok(()) } ``` - 根据协议特性决定是否需要等待ACK - 验证回复消息的完整性 - 处理后端返回的错误码 **典型错误处理模式**: ```rust match self.connection.send_message() { Ok(_) => {}, Err(VhostUserError::InvalidMessage) => { error!("Protocol violation detected"); reconnect(); }, Err(e) => return Err(convert_error(e)), } ``` - 严格校验消息格式 - 自动重连机制处理协议错误 - 错误类型转换层隔离底层传输错误
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值