Skip to main content

kernel_manager/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod kernels;
6pub mod pager;
7pub mod proxy;
8pub mod suspend;
9
10use crate::pager::{Pager, run_pager};
11use anyhow::{Error, anyhow};
12use fidl::Peered;
13use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, ServerEnd};
14use fidl_fuchsia_component as fcomponent;
15use fidl_fuchsia_component_decl as fdecl;
16use fidl_fuchsia_component_runner as frunner;
17use fidl_fuchsia_io as fio;
18use fidl_fuchsia_starnix_container as fstarnix;
19use fidl_fuchsia_starnix_runner as fstarnixrunner;
20use fuchsia_async as fasync;
21use fuchsia_component::client as fclient;
22use fuchsia_inspect::{HistogramProperty, NumericProperty};
23use futures::TryStreamExt;
24use kernels::Kernels;
25use log::warn;
26use proxy::ChannelProxy;
27use rand::RngExt as _;
28use starnix_sync::{LockDepMutex, StarnixKernelWakeLeaseLock, WakeSourcesLock};
29use std::future::Future;
30use std::sync::Arc;
31use suspend::{
32    ASLEEP_SIGNAL, AWAKE_SIGNAL, SuspendContext, WakeSource, WakeSources, suspend_container,
33};
34
35/// The name of the collection that the starnix_kernel is run in.
36const KERNEL_COLLECTION: &str = "kernels";
37
38/// The name of the protocol the kernel exposes for running containers.
39///
40/// This protocol is actually fuchsia.component.runner.ComponentRunner. We
41/// expose the implementation using this name to avoid confusion with copy
42/// of the fuchsia.component.runner.ComponentRunner protocol used for
43/// running component inside the container.
44const CONTAINER_RUNNER_PROTOCOL: &str = "fuchsia.starnix.container.Runner";
45
46#[allow(dead_code)]
47pub struct StarnixKernel {
48    /// The name of the kernel intsance in the kernels collection.
49    name: String,
50
51    /// The directory exposed by the Starnix kernel.
52    ///
53    /// This directory can be used to connect to services offered by the kernel.
54    exposed_dir: fio::DirectoryProxy,
55
56    /// An opaque token representing the container component.
57    component_instance: zx::Event,
58
59    /// The job the kernel lives under.
60    job: Arc<zx::Job>,
61
62    /// The currently active wake lease for the container.
63    wake_lease: LockDepMutex<Option<zx::EventPair>, StarnixKernelWakeLeaseLock>,
64}
65
66impl StarnixKernel {
67    /// Creates a new instance of `starnix_kernel`.
68    ///
69    /// This is done by creating a new child in the `kernels` collection.
70    ///
71    /// Returns the kernel and a future that will resolve when the kernel has stopped.
72    pub async fn create(
73        realm: fcomponent::RealmProxy,
74        kernel_url: &str,
75        start_info: frunner::ComponentStartInfo,
76        controller: ServerEnd<frunner::ComponentControllerMarker>,
77    ) -> Result<(Self, impl Future<Output = ()>), Error> {
78        let kernel_name = generate_kernel_name(&start_info)?;
79        let component_instance = start_info
80            .component_instance
81            .as_ref()
82            .ok_or_else(|| {
83                anyhow::anyhow!("expected to find component_instance in ComponentStartInfo")
84            })?
85            .duplicate_handle(zx::Rights::SAME_RIGHTS)?;
86
87        // Create the `starnix_kernel`.
88        let (controller_proxy, controller_server_end) = fidl::endpoints::create_proxy();
89        realm
90            .create_child(
91                &fdecl::CollectionRef { name: KERNEL_COLLECTION.into() },
92                &fdecl::Child {
93                    name: Some(kernel_name.clone()),
94                    url: Some(kernel_url.to_string()),
95                    startup: Some(fdecl::StartupMode::Lazy),
96                    ..Default::default()
97                },
98                fcomponent::CreateChildArgs {
99                    controller: Some(controller_server_end),
100                    ..Default::default()
101                },
102            )
103            .await?
104            .map_err(|e| anyhow::anyhow!("failed to create kernel: {:?}", e))?;
105
106        let exposed_dir = open_exposed_directory(&realm, &kernel_name, KERNEL_COLLECTION).await?;
107        let container_runner = fclient::connect_to_named_protocol_at_dir_root::<
108            frunner::ComponentRunnerMarker,
109        >(&exposed_dir, CONTAINER_RUNNER_PROTOCOL)?;
110
111        // Actually start the container.
112        container_runner.start(start_info, controller)?;
113
114        // Ask the kernel for its job.
115        let container_controller =
116            fclient::connect_to_protocol_at_dir_root::<fstarnix::ControllerMarker>(&exposed_dir)?;
117        let fstarnix::ControllerGetJobHandleResponse { job, .. } =
118            container_controller.get_job_handle().await?;
119        let Some(job) = job else {
120            anyhow::bail!("expected to find job in ControllerGetJobHandleResponse");
121        };
122
123        let kernel = Self {
124            name: kernel_name,
125            exposed_dir,
126            component_instance,
127            job: Arc::new(job),
128            wake_lease: Default::default(),
129        };
130        let on_stop = async move {
131            _ = controller_proxy.into_channel().unwrap().on_closed().await;
132        };
133        Ok((kernel, on_stop))
134    }
135
136    /// Gets the opaque token representing the container component.
137    pub fn component_instance(&self) -> &zx::Event {
138        &self.component_instance
139    }
140
141    /// Gets the job the kernel lives under.
142    pub fn job(&self) -> &Arc<zx::Job> {
143        &self.job
144    }
145
146    /// Connect to the specified protocol exposed by the kernel.
147    pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
148        fclient::connect_to_protocol_at_dir_root::<P>(&self.exposed_dir)
149    }
150}
151
152/// Generate a random name for the kernel.
153///
154/// We used to include some human-readable parts in the name, but people were
155/// tempted to make them load-bearing. We now just generate 7 random alphanumeric
156/// characters.
157fn generate_kernel_name(_start_info: &frunner::ComponentStartInfo) -> Result<String, Error> {
158    let random_id: String =
159        rand::rng().sample_iter(&rand::distr::Alphanumeric).take(7).map(char::from).collect();
160    Ok(random_id)
161}
162
163async fn open_exposed_directory(
164    realm: &fcomponent::RealmProxy,
165    child_name: &str,
166    collection_name: &str,
167) -> Result<fio::DirectoryProxy, Error> {
168    let (directory_proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
169    realm
170        .open_exposed_dir(
171            &fdecl::ChildRef { name: child_name.into(), collection: Some(collection_name.into()) },
172            server_end,
173        )
174        .await?
175        .map_err(|e| {
176            anyhow!(
177                "failed to bind to child {} in collection {:?}: {:?}",
178                child_name,
179                collection_name,
180                e
181            )
182        })?;
183    Ok(directory_proxy)
184}
185
186pub struct SuspendMessage {
187    pub payload: fstarnixrunner::ManagerSuspendContainerRequest,
188    pub responder: fstarnixrunner::ManagerSuspendContainerResponder,
189}
190
191pub async fn run_suspend_worker(
192    receiver: async_channel::Receiver<SuspendMessage>,
193    suspend_context: Arc<SuspendContext>,
194    kernels: &Kernels,
195) {
196    while let Ok(msg) = receiver.recv().await {
197        suspend_context.suspend_attempts_count.add(1);
198        match suspend_container(msg.payload, &suspend_context, &kernels).await {
199            Ok(response) => {
200                if let Err(e) = match response {
201                    Ok(o) => {
202                        suspend_context.suspend_successes_count.add(1);
203                        if let Some(suspend_time) = o.suspend_time {
204                            suspend_context.suspend_duration_histogram.insert(suspend_time as u64);
205                        }
206                        msg.responder.send(Ok(&o))
207                    }
208                    Err(e) => {
209                        suspend_context.suspend_failures_count.add(1);
210                        msg.responder.send(Err(e))
211                    }
212                } {
213                    warn!("error replying to suspend request: {e}");
214                }
215            }
216            Err(e) => {
217                suspend_context.suspend_failures_count.add(1);
218                warn!("error executing suspend: {e}");
219                let _ = msg.responder.send(Err(fstarnixrunner::SuspendError::SuspendFailure));
220            }
221        }
222    }
223}
224
225pub async fn serve_starnix_manager(
226    mut stream: fstarnixrunner::ManagerRequestStream,
227    suspend_context: Arc<SuspendContext>,
228    sender: &async_channel::Sender<(ChannelProxy, Arc<LockDepMutex<WakeSources, WakeSourcesLock>>)>,
229    pager: Arc<Pager>,
230    suspend_sender: &async_channel::Sender<SuspendMessage>,
231) -> Result<(), Error> {
232    while let Some(event) = stream.try_next().await? {
233        match event {
234            fstarnixrunner::ManagerRequest::SuspendContainer { payload, responder, .. } => {
235                if let Err(e) = suspend_sender.try_send(SuspendMessage { payload, responder }) {
236                    warn!("failed to send suspend request to worker: {e}");
237                }
238            }
239            fstarnixrunner::ManagerRequest::ProxyWakeChannel { payload, .. } => {
240                let fstarnixrunner::ManagerProxyWakeChannelRequest {
241                    // TODO: Handle more than one container.
242                    container_job: Some(_container_job),
243                    remote_channel: Some(remote_channel),
244                    container_channel: Some(container_channel),
245                    name: Some(name),
246                    counter: Some(message_counter),
247                    ..
248                } = payload
249                else {
250                    continue;
251                };
252
253                suspend_context.wake_sources.lock().insert(
254                    message_counter.koid().unwrap(),
255                    WakeSource::from_counter(
256                        message_counter.duplicate_handle(zx::Rights::SAME_RIGHTS)?,
257                        name.clone(),
258                    ),
259                );
260
261                let proxy =
262                    ChannelProxy { container_channel, remote_channel, message_counter, name };
263
264                sender.try_send((proxy, suspend_context.wake_sources.clone())).unwrap();
265            }
266            fstarnixrunner::ManagerRequest::RegisterWakeWatcher { payload, responder } => {
267                if let Some(watcher) = payload.watcher {
268                    let (clear_mask, set_mask) = (ASLEEP_SIGNAL, AWAKE_SIGNAL);
269                    watcher.signal_peer(clear_mask, set_mask)?;
270
271                    suspend_context.wake_watchers.lock().push(watcher);
272                }
273                if let Err(e) = responder.send() {
274                    warn!("error registering power watcher: {e}");
275                }
276            }
277            fstarnixrunner::ManagerRequest::AddWakeSource { payload, .. } => {
278                let fstarnixrunner::ManagerAddWakeSourceRequest {
279                    // TODO: Handle more than one container.
280                    container_job: Some(_container_job),
281                    name: Some(name),
282                    handle: Some(handle),
283                    signals: Some(signals),
284                    ..
285                } = payload
286                else {
287                    continue;
288                };
289                suspend_context.wake_sources.lock().insert(
290                    handle.koid().unwrap(),
291                    WakeSource::from_handle(handle, name, zx::Signals::from_bits_truncate(signals)),
292                );
293            }
294            fstarnixrunner::ManagerRequest::RemoveWakeSource { payload, .. } => {
295                let fstarnixrunner::ManagerRemoveWakeSourceRequest {
296                    // TODO: Handle more than one container.
297                    container_job: Some(_container_job),
298                    handle: Some(handle),
299                    ..
300                } = payload
301                else {
302                    continue;
303                };
304
305                suspend_context.wake_sources.lock().remove(&handle.koid().unwrap());
306            }
307            fstarnixrunner::ManagerRequest::CreatePager { payload, .. } => {
308                fasync::Task::spawn(run_pager(payload, pager.clone())).detach();
309            }
310            _ => {}
311        }
312    }
313    Ok(())
314}