102
#define syscall_arch (offsetof(struct seccomp_data, arch))
103
104
constexpr auto c_trueString = "1";
105
+constexpr size_t c_systemReservedMemory = 32 * 1024 * 1024; // 32MiB reserved for WSL system processes
106
+constexpr long c_cpuPeriodMicros = 100000;
107
+constexpr long c_systemReservedCpuMicros = 1000; // 0.01 Logical core reserved for WSL system processes
108
109
struct VmConfiguration
110
{
162
const char* SharedMemoryRoot = nullptr,
163
const char* InstallPath = nullptr,
164
const char* UserProfile = nullptr,
162
- std::optional<pid_t> DistroInitPid = {});
165
+ std::optional<pid_t> DistroInitPid = {},
166
+ const char* DistroCgroupPath = nullptr);
167
168
void LaunchSystemDistro(
169
int SocketFd,
174
const char* SharedMemoryRoot,
175
const char* InstallPath,
176
const char* UserProfile,
173
- pid_t DistroInitPid);
177
+ pid_t DistroInitPid,
178
+ const char* DistroCgroupPath);
179
180
std::map<unsigned long, std::string> ListDiskPartitions(const std::string& DeviceName, std::optional<unsigned long> WaitForIndex = {});
181
217
218
int WaitForChild(pid_t Pid, const char* Name);
219
220
+void SetupWslUserCgroup();
221
+
222
int Chroot(const char* Target)
223
224
/*++
1426
const char* SharedMemoryRoot,
1427
const char* InstallPath,
1428
const char* UserProfile,
1422
- std::optional<pid_t> DistroInitPid)
1429
+ std::optional<pid_t> DistroInitPid,
1430
+ const char* DistroCgroupPath)
1431
1432
/*++
1433
1466
1467
DistroInitPid - Supplies the pid of the user distribution's init process.
1468
1469
+ DistroCgroupPath - Supplies the cgroup path of this distribution.
1470
+
1471
Return Value:
1472
1473
None. This method does not return.
1574
AddEnvironmentVariable(LX_WSL2_INSTALL_PATH, InstallPath);
1575
AddEnvironmentVariable(LX_WSL2_USER_PROFILE, UserProfile);
1576
AddEnvironmentVariable(LX_WSL2_NETWORKING_MODE_ENV, std::to_string(static_cast<int>(Config.NetworkingMode)).c_str());
1577
+ AddEnvironmentVariable(LX_WSL2_DISTRO_CGROUP_PATH, DistroCgroupPath);
1578
1579
if (DistroInitPid.has_value())
1580
{
1676
const char* SharedMemoryRoot,
1677
const char* InstallPath,
1678
const char* UserProfile,
1668
- pid_t DistroInitPid)
1679
+ pid_t DistroInitPid,
1680
+ const char* DistroCgroupPath)
1681
1682
/*++
1683
1714
1715
DistroInitPid - Supplies the pid of the user distribution's init process.
1716
1717
+ DistroCgroupPath - Supplies the cgroup path of this distribution.
1718
+
1719
Return Value:
1720
1721
None. This method does not return.
1734
// Launch the init daemon, this method does not return.
1735
//
1736
1723
- LaunchInit(SocketFd, Target, true, Config, VmId, DistributionName, SharedMemoryRoot, InstallPath, UserProfile, DistroInitPid);
1737
+ LaunchInit(SocketFd, Target, true, Config, VmId, DistributionName, SharedMemoryRoot, InstallPath, UserProfile, DistroInitPid, DistroCgroupPath);
1738
_exit(1);
1739
}
1740
catch (...)
2241
2242
THROW_LAST_ERROR_IF(MountDevice(Message->MountDeviceType, Message->DeviceId, DISTRO_PATH, FsType, Message->Flags, MountOptions) < 0);
2243
2244
+ auto MiniInitDirectChildPidPath = std::filesystem::read_symlink(PROCFS_PATH "/self");
2245
+ pid_t MiniInitDirectChildPid = std::stoul(MiniInitDirectChildPidPath.string());
2246
+
2247
+ bool bootInit = false;
2248
+ bool enableGuiApps = Config.EnableGuiApps;
2249
+ {
2250
+ wil::unique_file File{fopen(DISTRO_PATH ETC_PATH "/wsl.conf", "r")};
2251
+ if (File)
2252
+ {
2253
+ std::vector<ConfigKey> ConfigKeys = {ConfigKey("boot.systemd", bootInit), ConfigKey("general.guiApplications", enableGuiApps)};
2254
+ ParseConfigFile(ConfigKeys, File.get(), CFG_SKIP_UNKNOWN_VALUES, STRING_TO_WSTRING(CONFIG_FILE));
2255
+ }
2256
+ }
2257
+
2258
+ //
2259
+ // Set up the per-distro cgroup before potentially forking into two inits.
2260
+ //
2261
+
2262
+ std::string DistroCgroupPath{};
2263
+ if (access(WSL_USER_CGROUP_PATH, F_OK) == 0)
2264
+ {
2265
+ DistroCgroupPath = UtilGetDistroCgroupPath(MiniInitDirectChildPid);
2266
+
2267
+ auto cleanup = wil::scope_exit([&]() {
2268
+ rmdir((DistroCgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR).c_str());
2269
+ rmdir((DistroCgroupPath + WSL_USER_SYSTEMD_CGROUP_DIR).c_str());
2270
+ rmdir(DistroCgroupPath.c_str());
2271
+ DistroCgroupPath.clear();
2272
+ });
2273
+
2274
+ try
2275
+ {
2276
+ THROW_LAST_ERROR_IF(UtilMkdir(DistroCgroupPath.c_str(), 0755) < 0);
2277
+
2278
+ if (bootInit)
2279
+ {
2280
+ THROW_LAST_ERROR_IF(UtilEnableAllCgroupControllers(DistroCgroupPath) < 0);
2281
+ THROW_LAST_ERROR_IF(UtilMkdir((DistroCgroupPath + WSL_USER_SYSTEMD_CGROUP_DIR).c_str(), 0755) < 0);
2282
+ THROW_LAST_ERROR_IF(UtilMkdir((DistroCgroupPath + WSL_USER_NON_SYSTEMD_CGROUP_DIR).c_str(), 0755) < 0);
2283
+ }
2284
+
2285
+ cleanup.release();
2286
+ }
2287
+ CATCH_LOG();
2288
+ }
2289
+
2290
//
2291
// Allow /etc/wsl.conf in the user distro to opt-out of GUI support.
2292
//
2294
// of GUI app support because WslService is waiting to accept a connection.
2295
//
2296
2237
- bool enableGuiApps = Config.EnableGuiApps;
2297
if (Message->Flags & LxMiniInitMessageFlagLaunchSystemDistro && Config.EnableGuiApps)
2298
{
2299
Step = LxInitCreateInstanceStepLaunchSystemDistro;
2241
- wil::unique_file File{fopen(DISTRO_PATH ETC_PATH "/wsl.conf", "r")};
2242
- if (File)
2243
- {
2244
- std::vector<ConfigKey> ConfigKeys = {ConfigKey("general.guiApplications", enableGuiApps)};
2245
- ParseConfigFile(ConfigKeys, File.get(), CFG_SKIP_UNKNOWN_VALUES, STRING_TO_WSTRING(CONFIG_FILE));
2246
- File.reset();
2247
- }
2300
2301
//
2302
// If the distro did not opt-out of GUI applications, continue launching the system distro.
2353
wsl::shared::string::FromSpan(Buffer, Message->SharedMemoryRootOffset),
2354
wsl::shared::string::FromSpan(Buffer, Message->InstallPathOffset),
2355
wsl::shared::string::FromSpan(Buffer, Message->UserProfileOffset),
2304
- ChildPid);
2356
+ ChildPid,
2357
+ DistroCgroupPath.empty() ? nullptr : DistroCgroupPath.c_str());
2358
}
2359
}
2360
2375
wsl::shared::string::FromSpan(Buffer, Message->DistributionNameOffset),
2376
nullptr,
2377
wsl::shared::string::FromSpan(Buffer, Message->InstallPathOffset),
2325
- wsl::shared::string::FromSpan(Buffer, Message->UserProfileOffset));
2378
+ wsl::shared::string::FromSpan(Buffer, Message->UserProfileOffset),
2379
+ std::nullopt,
2380
+ DistroCgroupPath.empty() ? nullptr : DistroCgroupPath.c_str());
2381
}
2382
catch (...)
2383
{
3062
Config.EnableSafeMode = true;
3063
}
3064
3065
+ if (EarlyConfig->IsolateDistroCgroup && access(CGROUP_MOUNTPOINT "/cgroup.controllers", F_OK) == 0)
3066
+ {
3067
+ SetupWslUserCgroup();
3068
+ }
3069
+
3070
//
3071
// Establish the connection for the guest network service.
3072
//
3287
return ProcessMountFolderMessage(Transaction, Buffer);
3288
3289
case LxInitCreateProcess:
3230
- return ProcessCreateProcessMessage(Transaction, Buffer);
3290
+ if (access(WSL_USER_NON_DISTRO_CGROUP_PATH, F_OK) == 0)
3291
+ {
3292
+ return ProcessCreateProcessMessage(Transaction, Buffer, WSL_USER_NON_DISTRO_CGROUP_PATH);
3293
+ }
3294
+ else
3295
+ {
3296
+ return ProcessCreateProcessMessage(Transaction, Buffer, std::nullopt);
3297
+ }
3298
3299
case LxMiniInitMessageWaitForPmemDevice:
3300
{
3724
}
3725
}
3726
3727
+void SetupWslUserCgroup()
3728
+
3729
+/*++
3730
+
3731
+Routine Description:
3732
+
3733
+ This routine creates a memory-limited cgroup for user processes. All user workloads
3734
+ (systemd, session leaders, boot commands) are placed into this cgroup so that they
3735
+ cannot exhaust all VM memory. This reserves a fixed amount of memory for critical
3736
+ WSL system processes (mini_init, GNS, Plan9, WSL init) that remain in the root cgroup.
3737
+
3738
+ The memory.max limit is set to totalram - c_systemReservedMemory, which provides a
3739
+ hard cap. When this limit is reached, the cgroup-local OOM killer activates and only
3740
+ kills processes within wsl-user, leaving system processes unaffected.
3741
+
3742
+ The cpu.max limit is set to (nproc * c_cpuPeriodMicros - c_systemReservedCpuMicros) per
3743
+ c_cpuPeriodMicros period, reserving a small portion of the CPU for WSL system processes so they remain
3744
+ schedulable even when user workloads saturate every CPU.
3745
+
3746
+Arguments:
3747
+
3748
+ None.
3749
+
3750
+Return Value:
3751
+
3752
+ None.
3753
+
3754
+--*/
3755
+
3756
+{
3757
+ struct sysinfo info = {};
3758
+ if (sysinfo(&info) < 0)
3759
+ {
3760
+ LOG_ERROR("sysinfo failed {}", errno);
3761
+ return;
3762
+ }
3763
+
3764
+ uint64_t totalRam = static_cast<uint64_t>(info.totalram) * info.mem_unit;
3765
+
3766
+ if (totalRam <= c_systemReservedMemory)
3767
+ {
3768
+ LOG_WARNING("Total RAM ({}) is too small to reserve {} for system processes", totalRam, c_systemReservedMemory);
3769
+ return;
3770
+ }
3771
+
3772
+ if (UtilEnableAllCgroupControllers(CGROUP_MOUNTPOINT) < 0)
3773
+ {
3774
+ LOG_ERROR("Failed to enable cgroup controllers for root {}", errno);
3775
+ return;
3776
+ }
3777
+
3778
+ if (UtilMkdir(WSL_USER_CGROUP_PATH, 0755) < 0)
3779
+ {
3780
+ LOG_ERROR("Failed to create wsl-user cgroup directory {}", errno);
3781
+ return;
3782
+ }
3783
+
3784
+ if (UtilEnableAllCgroupControllers(WSL_USER_CGROUP_PATH) < 0)
3785
+ {
3786
+ LOG_ERROR("Failed to enable cgroup controllers for wsl-user {}", errno);
3787
+ return;
3788
+ }
3789
+
3790
+ if (UtilMkdir(WSL_USER_NON_DISTRO_CGROUP_PATH, 0755) < 0)
3791
+ {
3792
+ LOG_ERROR("Failed to create wsl-user non-distro cgroup directory {}", errno);
3793
+ return;
3794
+ }
3795
+
3796
+ auto userMemoryMax = std::to_string(totalRam - c_systemReservedMemory);
3797
+ if (WriteToFile(WSL_USER_CGROUP_PATH "/memory.max", userMemoryMax.c_str()) < 0)
3798
+ {
3799
+ LOG_ERROR("Failed to set memory.max for wsl-user cgroup {}", errno);
3800
+ return;
3801
+ }
3802
+
3803
+ LOG_INFO("WSL user cgroup created with memory.max={} (totalram={}, reserved={})", userMemoryMax, totalRam, c_systemReservedMemory);
3804
+
3805
+ const long nproc = get_nprocs();
3806
+ if (nproc <= 0)
3807
+ {
3808
+ LOG_WARNING("get_nprocs returned {}, skipping cpu.max", nproc);
3809
+ return;
3810
+ }
3811
+
3812
+ const long cpuQuota = (nproc * c_cpuPeriodMicros) - c_systemReservedCpuMicros;
3813
+ auto userCpuMax = std::format("{} {}", cpuQuota, c_cpuPeriodMicros);
3814
+ if (WriteToFile(WSL_USER_CGROUP_PATH "/cpu.max", userCpuMax.c_str()) < 0)
3815
+ {
3816
+ LOG_ERROR("Failed to set cpu.max for wsl-user cgroup {}", errno);
3817
+ return;
3818
+ }
3819
+
3820
+ LOG_INFO("WSL user cgroup cpu.max={} (nproc={}, reserved={}us)", userCpuMax, nproc, c_systemReservedCpuMicros);
3821
+}
3822
+
3823
int main(int Argc, char* Argv[])
3824
{
3825
std::vector<gsl::byte> Buffer;
4027
}
4028
}
4029
3867
- UtilMount(nullptr, CGROUP_MOUNTPOINT, CGROUP2_DEVICE, 0, nullptr);
4030
+ if (UtilMount(nullptr, CGROUP_MOUNTPOINT, CGROUP2_DEVICE, 0, nullptr) < 0)
4031
+ {
4032
+ Result = -1;
4033
+ LOG_ERROR("Failed to mount cgroup2: {}", errno);
4034
+ goto ErrorExit;
4035
+ }
4036
4037
UtilSetThreadName("mini_init");
4038
4139
4140
sync();
4141
4142
+ //
4143
+ // Clear the distro cgroup
4144
+ //
4145
+
4146
+ auto CgroupDir = UtilGetDistroCgroupPath(Result);
4147
+ if (access(CgroupDir.c_str(), F_OK) == 0)
4148
+ {
4149
+ LOG_INFO("Process {} exited, removing cgroup {}", Result, CgroupDir);
4150
+
4151
+ //
4152
+ // Recursively rmdir the cgroup subtree.
4153
+ //
4154
+
4155
+ try
4156
+ {
4157
+ std::vector<std::string> dirs;
4158
+ for (const auto& entry : std::filesystem::recursive_directory_iterator(
4159
+ CgroupDir, std::filesystem::directory_options::skip_permission_denied))
4160
+ {
4161
+ if (entry.is_directory())
4162
+ {
4163
+ dirs.emplace_back(entry.path().string());
4164
+ }
4165
+ }
4166
+
4167
+ for (auto it = dirs.rbegin(); it != dirs.rend(); ++it)
4168
+ {
4169
+ if (rmdir(it->c_str()) < 0 && errno != ENOENT)
4170
+ {
4171
+ LOG_ERROR("rmdir({}) failed {}", *it, errno);
4172
+ }
4173
+ }
4174
+
4175
+ if (rmdir(CgroupDir.c_str()) < 0 && errno != ENOENT)
4176
+ {
4177
+ LOG_ERROR("rmdir({}) failed {}", CgroupDir, errno);
4178
+ }
4179
+ }
4180
+ CATCH_LOG();
4181
+ }
4182
+
4183
//
4184
// Send a message with the child's pid to the service.
4185
//