-
Notifications
You must be signed in to change notification settings - Fork 28
bins: intial CPU binding support #991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -581,9 +581,28 @@ async fn initialize_vm_objects( | |
|
|
||
| init.register_guest_hv_interface(guest_hv_lifecycle); | ||
| init.initialize_cpus().await?; | ||
|
|
||
| let total_cpus = pbind::online_cpus()?; | ||
| let vcpu_count: i32 = machine | ||
| .vcpus | ||
| .len() | ||
| .try_into() | ||
| .map_err(|_| anyhow::anyhow!("more than 2^31 vCPUs"))?; | ||
| let bind_cpus = if vcpu_count > total_cpus / 2 { | ||
| let mut bind_cpus = Vec::new(); | ||
| for i in 0..vcpu_count { | ||
| // Bind to the upper range of CPUs, fairly arbitrary. | ||
| bind_cpus.push(total_cpus - vcpu_count + i); | ||
| } | ||
| Some(bind_cpus) | ||
| } else { | ||
| None | ||
| }; | ||
|
Comment on lines
+591
to
+600
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we ought to log someplace whether we have decided to bind vCPUs or not? |
||
|
|
||
| let vcpu_tasks = Box::new(crate::vcpu_tasks::VcpuTasks::new( | ||
| &machine, | ||
| event_queue.clone() as Arc<dyn super::guest_event::VcpuEventHandler>, | ||
| bind_cpus, | ||
| log.new(slog::o!("component" => "vcpu_tasks")), | ||
| )?); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,6 +54,9 @@ pub struct Main { | |
| pub memory: usize, | ||
| pub use_reservoir: Option<bool>, | ||
| pub cpuid_profile: Option<String>, | ||
| /// How vCPUs should be bound to physical processors, if at all. If not | ||
| /// provided, vCPUs are not bound (equivalent to setting `any`). | ||
| pub cpu_binding: Option<BindingStrategy>, | ||
| /// Process exitcode to emit if/when instance halts | ||
| /// | ||
| /// Default: 0 | ||
|
|
@@ -69,6 +72,16 @@ pub struct Main { | |
| pub boot_order: Option<Vec<String>>, | ||
| } | ||
|
|
||
| #[derive(Copy, Clone, Debug, Deserialize, Serialize)] | ||
| #[serde(rename_all = "kebab-case")] | ||
| pub enum BindingStrategy { | ||
| /// vCPUs are not bound to any particular physical processor. | ||
| Any, | ||
| /// vCPUs are bound to the highest-numbered processors in the system, one | ||
| /// vCPU per CPU, with the last vCPU bound to the last physical processor. | ||
| FromLast, | ||
|
Comment on lines
+80
to
+82
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...descending? |
||
| } | ||
|
|
||
| /// A hard-coded device, either enabled by default or accessible locally | ||
| /// on a machine. | ||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -286,7 +286,29 @@ impl Instance { | |
| let state = &mut *state_guard; | ||
| let machine = state.machine.as_ref().unwrap(); | ||
|
|
||
| for vcpu in machine.vcpus.iter().map(Arc::clone) { | ||
| let bind_cpus = match this.0.config.main.cpu_binding { | ||
| Some(config::BindingStrategy::FromLast) => { | ||
| let mut bind_cpus = vec![None; machine.vcpus.len()]; | ||
| let total_cpus = | ||
| pbind::online_cpus().expect("can get processor count"); | ||
| let vcpu_count: i32 = | ||
| machine.vcpus.len().try_into().expect("<2^31 vCPUs"); | ||
|
Comment on lines
+294
to
+295
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit, considered not that big a deal: this is an error in the user's config file, rather than e.g. a syscall we expect should always work not working. can we make that a little clearer perhaps? |
||
|
|
||
| let first_bound_cpu = total_cpus - vcpu_count; | ||
| for i in 0..vcpu_count { | ||
| // Bind to the upper range of CPUs. | ||
| bind_cpus[i as usize] = Some(first_bound_cpu + i); | ||
| } | ||
| bind_cpus | ||
| } | ||
| Some(config::BindingStrategy::Any) | None => { | ||
| vec![None; machine.vcpus.len()] | ||
| } | ||
| }; | ||
|
|
||
| for (vcpu, bind_cpu) in | ||
| machine.vcpus.iter().map(Arc::clone).zip(bind_cpus.into_iter()) | ||
| { | ||
| let (task, ctrl) = | ||
| propolis::tasks::TaskHdl::new_held(Some(vcpu.barrier_fn())); | ||
|
|
||
|
|
@@ -295,6 +317,9 @@ impl Instance { | |
| let _ = std::thread::Builder::new() | ||
| .name(format!("vcpu-{}", vcpu.id)) | ||
| .spawn(move || { | ||
| if let Some(bind_cpu) = bind_cpu { | ||
| pbind::bind_lwp(bind_cpu).expect("can bind vcpu"); | ||
| } | ||
| Instance::vcpu_loop(inner, vcpu.as_ref(), &task, task_log) | ||
| }) | ||
| .unwrap(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [package] | ||
| name = "pbind" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm, since this is a low-level binding to the C headers, should this perhaps be |
||
| version = "0.0.0" | ||
| license = "MPL-2.0" | ||
| edition = "2021" | ||
|
|
||
| [dependencies] | ||
| libc.workspace = true | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,100 @@ | ||||||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||||||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||||
|
|
||||||
| // C-style type names follow, opt out of warnings for using names from headers. | ||||||
| #![allow(non_camel_case_types)] | ||||||
|
|
||||||
| //! Utility functions for binding LWPs to specific CPUs. | ||||||
| //! | ||||||
| //! This is generally a very light wrapper for illumos' `sysconf(3c)` and | ||||||
| //! `processor_bind(2)`, plus a few constants out of related headers. | ||||||
| use std::io::Error; | ||||||
|
|
||||||
| // From `<sys/types.h>` | ||||||
| pub type id_t = i32; | ||||||
|
|
||||||
| // From `<sys/processor.h>` | ||||||
| pub type processorid_t = i32; | ||||||
|
|
||||||
| // From `<sys/procset.h>` | ||||||
| pub type idtype_t = i32; | ||||||
|
|
||||||
| /// The enum values `idtype_t` can be. This is separate to be more explicit that | ||||||
| /// idtype_t is the ABI type, but is `repr(i32)` to make casting to `idtype_t` | ||||||
| /// trivial. | ||||||
| #[allow(non_camel_case_types)] | ||||||
| #[repr(i32)] | ||||||
| pub enum IdType { | ||||||
| P_PID, | ||||||
| P_PPID, | ||||||
| P_PGID, | ||||||
| P_SID, | ||||||
| P_CID, | ||||||
| P_UID, | ||||||
| P_GID, | ||||||
| P_ALL, | ||||||
| P_LWPID, | ||||||
| P_TASKID, | ||||||
| P_PROJID, | ||||||
| P_POOLID, | ||||||
| P_ZONEID, | ||||||
| P_CTID, | ||||||
| P_CPUID, | ||||||
| P_PSETID, | ||||||
| } | ||||||
|
|
||||||
| // Returns an `i32` to match `processorid_t`, so that `0..online_cpus()` | ||||||
| // produces a range of processor IDs without additional translation needed. | ||||||
| pub fn online_cpus() -> Result<i32, Error> { | ||||||
|
Comment on lines
+48
to
+50
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe worth noting in the comment that this is a wrapper around |
||||||
| let res = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) }; | ||||||
|
|
||||||
| if res == -1 { | ||||||
| return Err(Error::last_os_error()); | ||||||
| } | ||||||
|
|
||||||
| res.try_into().map_err(|_| { | ||||||
| // sysconf() reports more than 2^31 processors?! | ||||||
| Error::other(format!("too many processors: {}", res)) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. turbo nit: perhaps
Suggested change
|
||||||
| }) | ||||||
| } | ||||||
|
|
||||||
| #[cfg(target_os = "illumos")] | ||||||
| /// Bind the current LWP to the specified processor. | ||||||
| pub fn bind_lwp(bind_cpu: processorid_t) -> Result<(), Error> { | ||||||
| extern "C" { | ||||||
| fn processor_bind( | ||||||
| idtype: idtype_t, | ||||||
| id: id_t, | ||||||
| processorid: processorid_t, | ||||||
| obind: *mut processorid_t, | ||||||
| ) -> i32; | ||||||
| } | ||||||
|
|
||||||
| // From `<sys/types.h>`. | ||||||
| const P_MYID: id_t = -1; | ||||||
|
|
||||||
| let res = unsafe { | ||||||
| processor_bind( | ||||||
| IdType::P_LWPID as i32, | ||||||
| P_MYID, | ||||||
| bind_cpu, | ||||||
| std::ptr::null_mut(), | ||||||
| ) | ||||||
| }; | ||||||
|
|
||||||
| if res != 0 { | ||||||
| return Err(Error::last_os_error()); | ||||||
| } | ||||||
|
|
||||||
| Ok(()) | ||||||
| } | ||||||
|
|
||||||
| #[cfg(not(target_os = "illumos"))] | ||||||
| /// On non-illumos targets, we're not actually running a VM. We do need the | ||||||
| /// crate to compile to be nicer for blanket `cargo test` invocations on other | ||||||
| /// platforms. So a no-op function will do. | ||||||
| pub fn bind_lwp(_bind_cpu: processorid_t) -> Result<(), Error> { | ||||||
| Ok(()) | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems like this could be
or something but maybe that's weirder and worse