Skip to content

Commit eca5822

Browse files
committed
SunOS process and thread support
Read binary psinfo for System.Diagnostic.Process on SunOS Thanks for initial prototype help from: Austin Wise <[email protected]>
1 parent 1477734 commit eca5822

File tree

10 files changed

+747
-58
lines changed

10 files changed

+747
-58
lines changed
Lines changed: 230 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,256 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
47
using System.Runtime.InteropServices;
8+
using System.IO;
9+
10+
// The const int PRARGSZ show up as unused. Not sure why.
11+
#pragma warning disable CA1823
512

613
internal static partial class Interop
714
{
815
internal static partial class @procfs
916
{
17+
internal const string RootPath = "/proc/";
18+
private const string psinfoFileName = "/psinfo";
19+
private const string lwpDirName = "/lwp";
20+
private const string lwpsinfoFileName = "/lwpsinfo";
21+
22+
// Constants from sys/procfs.h
23+
private const int PRARGSZ = 80;
24+
private const int PRCLSZ = 8;
25+
private const int PRFNSZ = 16;
26+
27+
[StructLayout(LayoutKind.Sequential)]
28+
internal struct @timestruc_t
29+
{
30+
public long tv_sec;
31+
public long tv_nsec;
32+
}
33+
34+
// lwp ps(1) information file. /proc/<pid>/lwp/<lwpid>/lwpsinfo
35+
// "unsafe" because it has fixed sized arrays.
36+
[StructLayout(LayoutKind.Sequential)]
37+
public unsafe struct @lwpsinfo
38+
{
39+
private int pr_flag; /* lwp flags (DEPRECATED; do not use) */
40+
public uint pr_lwpid; /* lwp id */
41+
private long pr_addr; /* internal address of lwp */
42+
private long pr_wchan; /* wait addr for sleeping lwp */
43+
public byte pr_stype; /* synchronization event type */
44+
public byte pr_state; /* numeric lwp state */
45+
public byte pr_sname; /* printable character for pr_state */
46+
public byte pr_nice; /* nice for cpu usage */
47+
private short pr_syscall; /* system call number (if in syscall) */
48+
private byte pr_oldpri; /* pre-SVR4, low value is high priority */
49+
private byte pr_cpu; /* pre-SVR4, cpu usage for scheduling */
50+
public int pr_pri; /* priority, high value is high priority */
51+
private ushort pr_pctcpu; /* fixed pt. % of recent cpu time */
52+
private ushort pr_pad;
53+
public timestruc_t pr_start; /* lwp start time, from the epoch */
54+
public timestruc_t pr_time; /* usr+sys cpu time for this lwp */
55+
private fixed byte pr_clname[PRCLSZ]; /* scheduling class name */
56+
private fixed byte pr_name[PRFNSZ]; /* name of system lwp */
57+
private int pr_onpro; /* processor which last ran this lwp */
58+
private int pr_bindpro; /* processor to which lwp is bound */
59+
private int pr_bindpset; /* processor set to which lwp is bound */
60+
private int pr_lgrp; /* lwp home lgroup */
61+
private fixed int pr_filler[4]; /* reserved for future use */
62+
}
63+
64+
// process ps(1) information file. /proc/<pid>/psinfo
65+
// "unsafe" because it has fixed sized arrays.
66+
[StructLayout(LayoutKind.Sequential)]
67+
public unsafe struct @psinfo
68+
{
69+
private int pr_flag; /* process flags (DEPRECATED; do not use) */
70+
public int pr_nlwp; /* number of active lwps in the process */
71+
public int pr_pid; /* unique process id */
72+
public int pr_ppid; /* process id of parent */
73+
public int pr_pgid; /* pid of process group leader */
74+
public int pr_sid; /* session id */
75+
public uint pr_uid; /* real user id */
76+
public uint pr_euid; /* effective user id */
77+
public uint pr_gid; /* real group id */
78+
public uint pr_egid; /* effective group id */
79+
private long pr_addr; /* address of process */
80+
public ulong pr_size; /* size of process image in Kbytes */
81+
public ulong pr_rssize; /* resident set size in Kbytes */
82+
private ulong pr_pad1;
83+
private ulong pr_ttydev; /* controlling tty device (or PRNODEV) */
84+
private ushort pr_pctcpu; /* % of recent cpu time used by all lwps */
85+
private ushort pr_pctmem; /* % of system memory used by process */
86+
public timestruc_t pr_start; /* process start time, from the epoch */
87+
public timestruc_t pr_time; /* usr+sys cpu time for this process */
88+
public timestruc_t pr_ctime; /* usr+sys cpu time for reaped children */
89+
public fixed byte pr_fname[PRFNSZ]; /* name of execed file */
90+
public fixed byte pr_psargs[PRARGSZ]; /* initial characters of arg list */
91+
public int pr_wstat; /* if zombie, the wait() status */
92+
public int pr_argc; /* initial argument count */
93+
private long pr_argv; /* address of initial argument vector */
94+
private long pr_envp; /* address of initial environment vector */
95+
private byte pr_dmodel; /* data model of the process */
96+
private fixed byte pr_pad2[3];
97+
public int pr_taskid; /* task id */
98+
public int pr_projid; /* project id */
99+
public int pr_nzomb; /* number of zombie lwps in the process */
100+
public int pr_poolid; /* pool id */
101+
public int pr_zoneid; /* zone id */
102+
public int pr_contract; /* process contract */
103+
private fixed int pr_filler[1]; /* reserved for future use */
104+
public lwpsinfo pr_lwp; /* information for representative lwp */
105+
}
106+
107+
// Ouput type for TryReadProcessThreadInfo()
108+
internal struct ProcessThreadInfo
109+
{
110+
internal uint Tid;
111+
internal int Priority;
112+
internal int NiceVal;
113+
internal char Status;
114+
// add more fields when needed.
115+
}
116+
117+
// Ouput type for TryReadProcessStatusInfo()
118+
internal struct ProcessStatusInfo
119+
{
120+
internal int Pid;
121+
internal int ParentPid;
122+
internal int SessionId;
123+
internal nuint VirtualSize;
124+
internal nuint ResidentSetSize;
125+
internal Interop.Sys.TimeSpec StartTime;
126+
internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys
127+
internal string? Args;
128+
// add more fields when needed.
129+
internal ProcessThreadInfo Lwp1;
130+
}
131+
132+
internal static string GetInfoFilePathForProcess(int pid) =>
133+
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{psinfoFileName}");
134+
135+
internal static string GetLwpDirForProcess(int pid) =>
136+
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{lwpDirName}");
137+
138+
internal static string GetInfoFilePathForThread(int pid, int tid) =>
139+
string.Create(null, stackalloc char[256],
140+
$"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}");
141+
10142
/// <summary>
11143
/// Attempts to get status info for the specified process ID.
12144
/// </summary>
13145
/// <param name="pid">PID of the process to read status info for.</param>
14-
/// <param name="processStatus">The pointer to processStatus instance.</param>
146+
/// <param name="result">The pointer to processStatusInfo instance.</param>
15147
/// <returns>
16148
/// true if the process status was read; otherwise, false.
17149
/// </returns>
18-
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReadProcessStatusInfo", SetLastError = true)]
19-
[return: MarshalAs(UnmanagedType.Bool)]
20-
private static unsafe partial bool TryReadProcessStatusInfo(int pid, ProcessStatusInfo* processStatus);
21150

22-
internal struct ProcessStatusInfo
151+
// ProcessManager.SunOS.cs calls this
152+
// "unsafe" due to use of fixed-size buffers
153+
154+
internal static unsafe bool TryReadProcessStatusInfo(int pid, out ProcessStatusInfo result)
23155
{
24-
internal nuint ResidentSetSize;
25-
// add more fields when needed.
156+
result = default;
157+
bool ret = false;
158+
string fileName = "?";
159+
IntPtr ptr = 0;
160+
161+
try
162+
{
163+
fileName = GetInfoFilePathForProcess(pid);
164+
int size = Marshal.SizeOf<psinfo>();
165+
ptr = Marshal.AllocHGlobal(size);
166+
167+
BinaryReader br = new BinaryReader(File.OpenRead(fileName));
168+
byte[] buf = br.ReadBytes(size);
169+
Marshal.Copy(buf, 0, ptr, size);
170+
171+
procfs.psinfo pr = Marshal.PtrToStructure<psinfo>(ptr);
172+
173+
result.Pid = pr.pr_pid;
174+
result.ParentPid = pr.pr_ppid;
175+
result.SessionId = pr.pr_sid;
176+
result.VirtualSize = (nuint)pr.pr_size * 1024; // pr_rssize is in Kbytes
177+
result.ResidentSetSize = (nuint)pr.pr_rssize * 1024; // pr_rssize is in Kbytes
178+
result.StartTime.TvSec = pr.pr_start.tv_sec;
179+
result.StartTime.TvNsec = pr.pr_start.tv_nsec;
180+
result.CpuTotalTime.TvSec = pr.pr_time.tv_sec;
181+
result.CpuTotalTime.TvNsec = pr.pr_time.tv_nsec;
182+
result.Args = Marshal.PtrToStringUTF8((IntPtr)pr.pr_psargs);
183+
184+
// We get LWP[1] for "free"
185+
result.Lwp1.Tid = pr.pr_lwp.pr_lwpid;
186+
result.Lwp1.Priority = pr.pr_lwp.pr_pri;
187+
result.Lwp1.NiceVal = (int)pr.pr_lwp.pr_nice;
188+
result.Lwp1.Status = (char)pr.pr_lwp.pr_sname;
189+
190+
ret = true;
191+
}
192+
catch (Exception e)
193+
{
194+
Debug.Fail($"Failed to read \"{fileName}\": {e}");
195+
}
196+
finally
197+
{
198+
Marshal.FreeHGlobal(ptr);
199+
}
200+
201+
return ret;
26202
}
27203

28-
internal static unsafe bool TryReadProcessStatusInfo(int pid, out ProcessStatusInfo statusInfo)
204+
/// <summary>
205+
/// Attempts to get status info for the specified Thread ID.
206+
/// </summary>
207+
/// <param name="pid">PID of the process to read status info for.</param>
208+
/// <param name="tid">TID of the thread to read status info for.</param>
209+
/// <param name="result">The pointer to processStatusInfo instance.</param>
210+
/// <returns>
211+
/// true if the process status was read; otherwise, false.
212+
/// </returns>
213+
214+
// ProcessManager.SunOS.cs calls this
215+
// "unsafe" due to use of fixed-size buffers
216+
217+
internal static unsafe bool TryReadProcessThreadInfo(int pid, int tid, out ProcessThreadInfo result)
29218
{
30-
statusInfo = default;
31-
fixed (ProcessStatusInfo* pStatusInfo = &statusInfo)
219+
result = default;
220+
bool ret = false;
221+
string fileName = "?";
222+
IntPtr ptr = 0;
223+
224+
try
32225
{
33-
return TryReadProcessStatusInfo(pid, pStatusInfo);
226+
fileName = GetInfoFilePathForThread(pid, tid);
227+
int size = Marshal.SizeOf<lwpsinfo>();
228+
ptr = Marshal.AllocHGlobal(size);
229+
230+
BinaryReader br = new BinaryReader(File.OpenRead(fileName));
231+
byte[] buf = br.ReadBytes(size);
232+
Marshal.Copy(buf, 0, ptr, size);
233+
234+
procfs.lwpsinfo lwp = Marshal.PtrToStructure<lwpsinfo>(ptr);
235+
236+
result.Tid = lwp.pr_lwpid;
237+
result.Priority = lwp.pr_pri;
238+
result.NiceVal = (int)lwp.pr_nice;
239+
result.Status = (char)lwp.pr_sname;
240+
241+
ret = true;
242+
}
243+
catch (Exception e)
244+
{
245+
Debug.Fail($"Failed to read \"{fileName}\": {e}");
34246
}
247+
finally
248+
{
249+
Marshal.FreeHGlobal(ptr);
250+
}
251+
252+
return ret;
35253
}
254+
36255
}
37256
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
6+
internal static partial class Interop
7+
{
8+
internal static partial class @procfs
9+
{
10+
}
11+
}

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
4+
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)</TargetFrameworks>
55
<DefineConstants>$(DefineConstants);FEATURE_REGISTRY</DefineConstants>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
@@ -363,6 +363,19 @@
363363
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
364364
</ItemGroup>
365365

366+
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'illumos' or '$(TargetPlatformIdentifier)' == 'sunos'">
367+
<Compile Include="System\Diagnostics\Process.BSD.cs" />
368+
<Compile Include="System\Diagnostics\Process.SunOS.cs" />
369+
<Compile Include="System\Diagnostics\ProcessManager.SunOS.cs" />
370+
<Compile Include="System\Diagnostics\ProcessThread.SunOS.cs" />
371+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.TimeSpec.cs"
372+
Link="Common\Interop\Unix\System.Native\Interop.TimeSpec.cs" />
373+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.cs"
374+
Link="Common\Interop\SunOS\procfs\Interop.ProcFsStat.cs" />
375+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs"
376+
Link="Common\Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" />
377+
</ItemGroup>
378+
366379
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">
367380
<Compile Include="System\Diagnostics\Process.iOS.cs" />
368381
<Compile Include="System\Diagnostics\ProcessManager.iOS.cs" />

0 commit comments

Comments
 (0)