procfs: Migrate seqfile implementations.

Migrates all (except 3) seqfile implementations to the vfs.DynamicBytesSource
interface. There should not be any change in functionality due to this migration
itself.

Please note that the following seqfile implementations have not been migrated:
- /proc/filesystems in proc/filesystems.go
- /proc/[pid]/mountinfo in proc/mounts.go
- /proc/[pid]/mounts in proc/mounts.go
This is because these depend on pending changes in /pkg/senty/vfs.

PiperOrigin-RevId: 263880719
This commit is contained in:
Ayush Ranjan 2019-08-16 17:33:23 -07:00 committed by gVisor bot
parent 2a1303357c
commit 661b2b9f69
13 changed files with 1235 additions and 20 deletions

View File

@ -0,0 +1,49 @@
load("//tools/go_stateify:defs.bzl", "go_library", "go_test")
package(licenses = ["notice"])
go_library(
name = "proc",
srcs = [
"filesystems.go",
"loadavg.go",
"meminfo.go",
"mounts.go",
"net.go",
"proc.go",
"stat.go",
"sys.go",
"task.go",
"version.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc",
deps = [
"//pkg/abi/linux",
"//pkg/binary",
"//pkg/log",
"//pkg/sentry/context",
"//pkg/sentry/fs",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/limits",
"//pkg/sentry/mm",
"//pkg/sentry/socket",
"//pkg/sentry/socket/unix",
"//pkg/sentry/socket/unix/transport",
"//pkg/sentry/usage",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
],
)
go_test(
name = "proc_test",
size = "small",
srcs = ["net_test.go"],
embed = [":proc"],
deps = [
"//pkg/abi/linux",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/inet",
],
)

View File

@ -0,0 +1,25 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
// filesystemsData implements vfs.DynamicBytesSource for /proc/filesystems.
//
// +stateify savable
type filesystemsData struct{}
// TODO(b/138862512): Implement vfs.DynamicBytesSource.Generate for
// filesystemsData. We would need to retrive filesystem names from
// vfs.VirtualFilesystem. Also needs vfs replacement for
// fs.Filesystem.AllowUserList() and fs.FilesystemRequiresDev.

View File

@ -0,0 +1,40 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// loadavgData backs /proc/loadavg.
//
// +stateify savable
type loadavgData struct{}
var _ vfs.DynamicBytesSource = (*loadavgData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/62345059): Include real data in fields.
// Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods.
// Column 4-5: currently running processes and the total number of processes.
// Column 6: the last process ID used.
fmt.Fprintf(buf, "%.2f %.2f %.2f %d/%d %d\n", 0.00, 0.00, 0.00, 0, 0, 0)
return nil
}

View File

@ -0,0 +1,77 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/usage"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// meminfoData implements vfs.DynamicBytesSource for /proc/meminfo.
//
// +stateify savable
type meminfoData struct {
// k is the owning Kernel.
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*meminfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
mf := d.k.MemoryFile()
mf.UpdateUsage()
snapshot, totalUsage := usage.MemoryAccounting.Copy()
totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage)
anon := snapshot.Anonymous + snapshot.Tmpfs
file := snapshot.PageCache + snapshot.Mapped
// We don't actually have active/inactive LRUs, so just make up numbers.
activeFile := (file / 2) &^ (usermem.PageSize - 1)
inactiveFile := file - activeFile
fmt.Fprintf(buf, "MemTotal: %8d kB\n", totalSize/1024)
memFree := (totalSize - totalUsage) / 1024
// We use MemFree as MemAvailable because we don't swap.
// TODO(rahat): When reclaim is implemented the value of MemAvailable
// should change.
fmt.Fprintf(buf, "MemFree: %8d kB\n", memFree)
fmt.Fprintf(buf, "MemAvailable: %8d kB\n", memFree)
fmt.Fprintf(buf, "Buffers: 0 kB\n") // memory usage by block devices
fmt.Fprintf(buf, "Cached: %8d kB\n", (file+snapshot.Tmpfs)/1024)
// Emulate a system with no swap, which disables inactivation of anon pages.
fmt.Fprintf(buf, "SwapCache: 0 kB\n")
fmt.Fprintf(buf, "Active: %8d kB\n", (anon+activeFile)/1024)
fmt.Fprintf(buf, "Inactive: %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Active(anon): %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Inactive(anon): 0 kB\n")
fmt.Fprintf(buf, "Active(file): %8d kB\n", activeFile/1024)
fmt.Fprintf(buf, "Inactive(file): %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Unevictable: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "Mlocked: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "SwapTotal: 0 kB\n")
fmt.Fprintf(buf, "SwapFree: 0 kB\n")
fmt.Fprintf(buf, "Dirty: 0 kB\n")
fmt.Fprintf(buf, "Writeback: 0 kB\n")
fmt.Fprintf(buf, "AnonPages: %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Mapped: %8d kB\n", file/1024) // doesn't count mapped tmpfs, which we don't know
fmt.Fprintf(buf, "Shmem: %8d kB\n", snapshot.Tmpfs/1024)
return nil
}

View File

@ -0,0 +1,33 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import "gvisor.dev/gvisor/pkg/sentry/kernel"
// TODO(b/138862512): Implement mountInfoFile and mountsFile.
// mountInfoFile implements vfs.DynamicBytesSource for /proc/[pid]/mountinfo.
//
// +stateify savable
type mountInfoFile struct {
t *kernel.Task
}
// mountsFile implements vfs.DynamicBytesSource for /proc/[pid]/mounts.
//
// +stateify savable
type mountsFile struct {
t *kernel.Task
}

View File

@ -0,0 +1,338 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/binary"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket"
"gvisor.dev/gvisor/pkg/sentry/socket/unix"
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// ifinet6 implements vfs.DynamicBytesSource for /proc/net/if_inet6.
//
// +stateify savable
type ifinet6 struct {
s inet.Stack
}
var _ vfs.DynamicBytesSource = (*ifinet6)(nil)
func (n *ifinet6) contents() []string {
var lines []string
nics := n.s.Interfaces()
for id, naddrs := range n.s.InterfaceAddrs() {
nic, ok := nics[id]
if !ok {
// NIC was added after NICNames was called. We'll just
// ignore it.
continue
}
for _, a := range naddrs {
// IPv6 only.
if a.Family != linux.AF_INET6 {
continue
}
// Fields:
// IPv6 address displayed in 32 hexadecimal chars without colons
// Netlink device number (interface index) in hexadecimal (use nic id)
// Prefix length in hexadecimal
// Scope value (use 0)
// Interface flags
// Device name
lines = append(lines, fmt.Sprintf("%032x %02x %02x %02x %02x %8s\n", a.Addr, id, a.PrefixLen, 0, a.Flags, nic.Name))
}
}
return lines
}
// Generate implements vfs.DynamicBytesSource.Generate.
func (n *ifinet6) Generate(ctx context.Context, buf *bytes.Buffer) error {
for _, l := range n.contents() {
buf.WriteString(l)
}
return nil
}
// netDev implements vfs.DynamicBytesSource for /proc/net/dev.
//
// +stateify savable
type netDev struct {
s inet.Stack
}
var _ vfs.DynamicBytesSource = (*netDev)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (n *netDev) Generate(ctx context.Context, buf *bytes.Buffer) error {
interfaces := n.s.Interfaces()
buf.WriteString("Inter-| Receive | Transmit\n")
buf.WriteString(" face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n")
for _, i := range interfaces {
// Implements the same format as
// net/core/net-procfs.c:dev_seq_printf_stats.
var stats inet.StatDev
if err := n.s.Statistics(&stats, i.Name); err != nil {
log.Warningf("Failed to retrieve interface statistics for %v: %v", i.Name, err)
continue
}
fmt.Fprintf(
buf,
"%6s: %7d %7d %4d %4d %4d %5d %10d %9d %8d %7d %4d %4d %4d %5d %7d %10d\n",
i.Name,
// Received
stats[0], // bytes
stats[1], // packets
stats[2], // errors
stats[3], // dropped
stats[4], // fifo
stats[5], // frame
stats[6], // compressed
stats[7], // multicast
// Transmitted
stats[8], // bytes
stats[9], // packets
stats[10], // errors
stats[11], // dropped
stats[12], // fifo
stats[13], // frame
stats[14], // compressed
stats[15], // multicast
)
}
return nil
}
// netUnix implements vfs.DynamicBytesSource for /proc/net/unix.
//
// +stateify savable
type netUnix struct {
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*netUnix)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (n *netUnix) Generate(ctx context.Context, buf *bytes.Buffer) error {
buf.WriteString("Num RefCount Protocol Flags Type St Inode Path\n")
for _, se := range n.k.ListSockets() {
s := se.Sock.Get()
if s == nil {
log.Debugf("Couldn't resolve weakref %v in socket table, racing with destruction?", se.Sock)
continue
}
sfile := s.(*fs.File)
if family, _, _ := sfile.FileOperations.(socket.Socket).Type(); family != linux.AF_UNIX {
s.DecRef()
// Not a unix socket.
continue
}
sops := sfile.FileOperations.(*unix.SocketOperations)
addr, err := sops.Endpoint().GetLocalAddress()
if err != nil {
log.Warningf("Failed to retrieve socket name from %+v: %v", sfile, err)
addr.Addr = "<unknown>"
}
sockFlags := 0
if ce, ok := sops.Endpoint().(transport.ConnectingEndpoint); ok {
if ce.Listening() {
// For unix domain sockets, linux reports a single flag
// value if the socket is listening, of __SO_ACCEPTCON.
sockFlags = linux.SO_ACCEPTCON
}
}
// In the socket entry below, the value for the 'Num' field requires
// some consideration. Linux prints the address to the struct
// unix_sock representing a socket in the kernel, but may redact the
// value for unprivileged users depending on the kptr_restrict
// sysctl.
//
// One use for this field is to allow a privileged user to
// introspect into the kernel memory to determine information about
// a socket not available through procfs, such as the socket's peer.
//
// In gvisor, returning a pointer to our internal structures would
// be pointless, as it wouldn't match the memory layout for struct
// unix_sock, making introspection difficult. We could populate a
// struct unix_sock with the appropriate data, but even that
// requires consideration for which kernel version to emulate, as
// the definition of this struct changes over time.
//
// For now, we always redact this pointer.
fmt.Fprintf(buf, "%#016p: %08X %08X %08X %04X %02X %5d",
(*unix.SocketOperations)(nil), // Num, pointer to kernel socket struct.
sfile.ReadRefs()-1, // RefCount, don't count our own ref.
0, // Protocol, always 0 for UDS.
sockFlags, // Flags.
sops.Endpoint().Type(), // Type.
sops.State(), // State.
sfile.InodeID(), // Inode.
)
// Path
if len(addr.Addr) != 0 {
if addr.Addr[0] == 0 {
// Abstract path.
fmt.Fprintf(buf, " @%s", string(addr.Addr[1:]))
} else {
fmt.Fprintf(buf, " %s", string(addr.Addr))
}
}
fmt.Fprintf(buf, "\n")
s.DecRef()
}
return nil
}
// netTCP implements vfs.DynamicBytesSource for /proc/net/tcp.
//
// +stateify savable
type netTCP struct {
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*netTCP)(nil)
func (n *netTCP) Generate(ctx context.Context, buf *bytes.Buffer) error {
t := kernel.TaskFromContext(ctx)
buf.WriteString(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode \n")
for _, se := range n.k.ListSockets() {
s := se.Sock.Get()
if s == nil {
log.Debugf("Couldn't resolve weakref %+v in socket table, racing with destruction?", se.Sock)
continue
}
sfile := s.(*fs.File)
sops, ok := sfile.FileOperations.(socket.Socket)
if !ok {
panic(fmt.Sprintf("Found non-socket file in socket table: %+v", sfile))
}
if family, stype, _ := sops.Type(); !(family == linux.AF_INET && stype == linux.SOCK_STREAM) {
s.DecRef()
// Not tcp4 sockets.
continue
}
// Linux's documentation for the fields below can be found at
// https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt.
// For Linux's implementation, see net/ipv4/tcp_ipv4.c:get_tcp4_sock().
// Note that the header doesn't contain labels for all the fields.
// Field: sl; entry number.
fmt.Fprintf(buf, "%4d: ", se.ID)
portBuf := make([]byte, 2)
// Field: local_adddress.
var localAddr linux.SockAddrInet
if local, _, err := sops.GetSockName(t); err == nil {
localAddr = *local.(*linux.SockAddrInet)
}
binary.LittleEndian.PutUint16(portBuf, localAddr.Port)
fmt.Fprintf(buf, "%08X:%04X ",
binary.LittleEndian.Uint32(localAddr.Addr[:]),
portBuf)
// Field: rem_address.
var remoteAddr linux.SockAddrInet
if remote, _, err := sops.GetPeerName(t); err == nil {
remoteAddr = *remote.(*linux.SockAddrInet)
}
binary.LittleEndian.PutUint16(portBuf, remoteAddr.Port)
fmt.Fprintf(buf, "%08X:%04X ",
binary.LittleEndian.Uint32(remoteAddr.Addr[:]),
portBuf)
// Field: state; socket state.
fmt.Fprintf(buf, "%02X ", sops.State())
// Field: tx_queue, rx_queue; number of packets in the transmit and
// receive queue. Unimplemented.
fmt.Fprintf(buf, "%08X:%08X ", 0, 0)
// Field: tr, tm->when; timer active state and number of jiffies
// until timer expires. Unimplemented.
fmt.Fprintf(buf, "%02X:%08X ", 0, 0)
// Field: retrnsmt; number of unrecovered RTO timeouts.
// Unimplemented.
fmt.Fprintf(buf, "%08X ", 0)
// Field: uid.
uattr, err := sfile.Dirent.Inode.UnstableAttr(ctx)
if err != nil {
log.Warningf("Failed to retrieve unstable attr for socket file: %v", err)
fmt.Fprintf(buf, "%5d ", 0)
} else {
fmt.Fprintf(buf, "%5d ", uint32(uattr.Owner.UID.In(t.UserNamespace()).OrOverflow()))
}
// Field: timeout; number of unanswered 0-window probes.
// Unimplemented.
fmt.Fprintf(buf, "%8d ", 0)
// Field: inode.
fmt.Fprintf(buf, "%8d ", sfile.InodeID())
// Field: refcount. Don't count the ref we obtain while deferencing
// the weakref to this socket.
fmt.Fprintf(buf, "%d ", sfile.ReadRefs()-1)
// Field: Socket struct address. Redacted due to the same reason as
// the 'Num' field in /proc/net/unix, see netUnix.ReadSeqFileData.
fmt.Fprintf(buf, "%#016p ", (*socket.Socket)(nil))
// Field: retransmit timeout. Unimplemented.
fmt.Fprintf(buf, "%d ", 0)
// Field: predicted tick of soft clock (delayed ACK control data).
// Unimplemented.
fmt.Fprintf(buf, "%d ", 0)
// Field: (ack.quick<<1)|ack.pingpong, Unimplemented.
fmt.Fprintf(buf, "%d ", 0)
// Field: sending congestion window, Unimplemented.
fmt.Fprintf(buf, "%d ", 0)
// Field: Slow start size threshold, -1 if threshold >= 0xFFFF.
// Unimplemented, report as large threshold.
fmt.Fprintf(buf, "%d", -1)
fmt.Fprintf(buf, "\n")
s.DecRef()
}
return nil
}

View File

@ -0,0 +1,78 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"reflect"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/inet"
)
func newIPv6TestStack() *inet.TestStack {
s := inet.NewTestStack()
s.SupportsIPv6Flag = true
return s
}
func TestIfinet6NoAddresses(t *testing.T) {
n := &ifinet6{s: newIPv6TestStack()}
var buf bytes.Buffer
n.Generate(contexttest.Context(t), &buf)
if buf.Len() > 0 {
t.Errorf("n.Generate() generated = %v, want = %v", buf.Bytes(), []byte{})
}
}
func TestIfinet6(t *testing.T) {
s := newIPv6TestStack()
s.InterfacesMap[1] = inet.Interface{Name: "eth0"}
s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{
{
Family: linux.AF_INET6,
PrefixLen: 128,
Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"),
},
}
s.InterfacesMap[2] = inet.Interface{Name: "eth1"}
s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{
{
Family: linux.AF_INET6,
PrefixLen: 128,
Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
},
}
want := map[string]struct{}{
"000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {},
"101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {},
}
n := &ifinet6{s: s}
contents := n.contents()
if len(contents) != len(want) {
t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want))
}
got := map[string]struct{}{}
for _, l := range contents {
got[l] = struct{}{}
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Got n.contents() = %v, want = %v", got, want)
}
}

View File

@ -0,0 +1,16 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package proc implements a partial in-memory file system for procfs.
package proc

View File

@ -0,0 +1,127 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// cpuStats contains the breakdown of CPU time for /proc/stat.
type cpuStats struct {
// user is time spent in userspace tasks with non-positive niceness.
user uint64
// nice is time spent in userspace tasks with positive niceness.
nice uint64
// system is time spent in non-interrupt kernel context.
system uint64
// idle is time spent idle.
idle uint64
// ioWait is time spent waiting for IO.
ioWait uint64
// irq is time spent in interrupt context.
irq uint64
// softirq is time spent in software interrupt context.
softirq uint64
// steal is involuntary wait time.
steal uint64
// guest is time spent in guests with non-positive niceness.
guest uint64
// guestNice is time spent in guests with positive niceness.
guestNice uint64
}
// String implements fmt.Stringer.
func (c cpuStats) String() string {
return fmt.Sprintf("%d %d %d %d %d %d %d %d %d %d", c.user, c.nice, c.system, c.idle, c.ioWait, c.irq, c.softirq, c.steal, c.guest, c.guestNice)
}
// statData implements vfs.DynamicBytesSource for /proc/stat.
//
// +stateify savable
type statData struct {
// k is the owning Kernel.
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*statData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/37226836): We currently export only zero CPU stats. We could
// at least provide some aggregate stats.
var cpu cpuStats
fmt.Fprintf(buf, "cpu %s\n", cpu)
for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ {
fmt.Fprintf(buf, "cpu%d %s\n", c, cpu)
}
// The total number of interrupts is dependent on the CPUs and PCI
// devices on the system. See arch_probe_nr_irqs.
//
// Since we don't report real interrupt stats, just choose an arbitrary
// value from a representative VM.
const numInterrupts = 256
// The Kernel doesn't handle real interrupts, so report all zeroes.
// TODO(b/37226836): We could count page faults as #PF.
fmt.Fprintf(buf, "intr 0") // total
for i := 0; i < numInterrupts; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
// Total number of context switches.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "ctxt 0\n")
// CLOCK_REALTIME timestamp from boot, in seconds.
fmt.Fprintf(buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds())
// Total number of clones.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "processes 0\n")
// Number of runnable tasks.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_running 0\n")
// Number of tasks waiting on IO.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_blocked 0\n")
// Number of each softirq handled.
fmt.Fprintf(buf, "softirq 0") // total
for i := 0; i < linux.NumSoftIRQ; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
return nil
}

View File

@ -0,0 +1,51 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// mmapMinAddrData implements vfs.DynamicBytesSource for
// /proc/sys/vm/mmap_min_addr.
//
// +stateify savable
type mmapMinAddrData struct {
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*mmapMinAddrData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *mmapMinAddrData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%d\n", d.k.Platform.MinUserAddress())
return nil
}
// +stateify savable
type overcommitMemory struct{}
var _ vfs.DynamicBytesSource = (*overcommitMemory)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *overcommitMemory) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "0\n")
return nil
}

View File

@ -0,0 +1,261 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/sentry/mm"
"gvisor.dev/gvisor/pkg/sentry/usage"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// mapsCommon is embedded by mapsData and smapsData.
type mapsCommon struct {
t *kernel.Task
}
// mm gets the kernel task's MemoryManager. No additional reference is taken on
// mm here. This is safe because MemoryManager.destroy is required to leave the
// MemoryManager in a state where it's still usable as a DynamicBytesSource.
func (md *mapsCommon) mm() *mm.MemoryManager {
var tmm *mm.MemoryManager
md.t.WithMuLocked(func(t *kernel.Task) {
if mm := t.MemoryManager(); mm != nil {
tmm = mm
}
})
return tmm
}
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
//
// +stateify savable
type mapsData struct {
mapsCommon
}
var _ vfs.DynamicBytesSource = (*mapsData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (md *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
if mm := md.mm(); mm != nil {
mm.ReadMapsDataInto(ctx, buf)
}
return nil
}
// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps.
//
// +stateify savable
type smapsData struct {
mapsCommon
}
var _ vfs.DynamicBytesSource = (*smapsData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (sd *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
if mm := sd.mm(); mm != nil {
mm.ReadSmapsDataInto(ctx, buf)
}
return nil
}
// +stateify savable
type taskStatData struct {
t *kernel.Task
// If tgstats is true, accumulate fault stats (not implemented) and CPU
// time across all tasks in t's thread group.
tgstats bool
// pidns is the PID namespace associated with the proc filesystem that
// includes the file using this statData.
pidns *kernel.PIDNamespace
}
var _ vfs.DynamicBytesSource = (*taskStatData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *taskStatData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%d ", s.pidns.IDOfTask(s.t))
fmt.Fprintf(buf, "(%s) ", s.t.Name())
fmt.Fprintf(buf, "%c ", s.t.StateStatus()[0])
ppid := kernel.ThreadID(0)
if parent := s.t.Parent(); parent != nil {
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
}
fmt.Fprintf(buf, "%d ", ppid)
fmt.Fprintf(buf, "%d ", s.pidns.IDOfProcessGroup(s.t.ThreadGroup().ProcessGroup()))
fmt.Fprintf(buf, "%d ", s.pidns.IDOfSession(s.t.ThreadGroup().Session()))
fmt.Fprintf(buf, "0 0 " /* tty_nr tpgid */)
fmt.Fprintf(buf, "0 " /* flags */)
fmt.Fprintf(buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */)
var cputime usage.CPUStats
if s.tgstats {
cputime = s.t.ThreadGroup().CPUStats()
} else {
cputime = s.t.CPUStats()
}
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
cputime = s.t.ThreadGroup().JoinedChildCPUStats()
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
fmt.Fprintf(buf, "%d %d ", s.t.Priority(), s.t.Niceness())
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Count())
// itrealvalue. Since kernel 2.6.17, this field is no longer
// maintained, and is hard coded as 0.
fmt.Fprintf(buf, "0 ")
// Start time is relative to boot time, expressed in clock ticks.
fmt.Fprintf(buf, "%d ", linux.ClockTFromDuration(s.t.StartTime().Sub(s.t.Kernel().Timekeeper().BootTime())))
var vss, rss uint64
s.t.WithMuLocked(func(t *kernel.Task) {
if mm := t.MemoryManager(); mm != nil {
vss = mm.VirtualMemorySize()
rss = mm.ResidentSetSize()
}
})
fmt.Fprintf(buf, "%d %d ", vss, rss/usermem.PageSize)
// rsslim.
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Limits().Get(limits.Rss).Cur)
fmt.Fprintf(buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */)
fmt.Fprintf(buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */)
fmt.Fprintf(buf, "0 0 " /* nswap cnswap */)
terminationSignal := linux.Signal(0)
if s.t == s.t.ThreadGroup().Leader() {
terminationSignal = s.t.ThreadGroup().TerminationSignal()
}
fmt.Fprintf(buf, "%d ", terminationSignal)
fmt.Fprintf(buf, "0 0 0 " /* processor rt_priority policy */)
fmt.Fprintf(buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */)
fmt.Fprintf(buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */)
fmt.Fprintf(buf, "0\n" /* exit_code */)
return nil
}
// statmData implements vfs.DynamicBytesSource for /proc/[pid]/statm.
//
// +stateify savable
type statmData struct {
t *kernel.Task
}
var _ vfs.DynamicBytesSource = (*statmData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error {
var vss, rss uint64
s.t.WithMuLocked(func(t *kernel.Task) {
if mm := t.MemoryManager(); mm != nil {
vss = mm.VirtualMemorySize()
rss = mm.ResidentSetSize()
}
})
fmt.Fprintf(buf, "%d %d 0 0 0 0 0\n", vss/usermem.PageSize, rss/usermem.PageSize)
return nil
}
// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status.
//
// +stateify savable
type statusData struct {
t *kernel.Task
pidns *kernel.PIDNamespace
}
var _ vfs.DynamicBytesSource = (*statusData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "Name:\t%s\n", s.t.Name())
fmt.Fprintf(buf, "State:\t%s\n", s.t.StateStatus())
fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.t.ThreadGroup()))
fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.t))
ppid := kernel.ThreadID(0)
if parent := s.t.Parent(); parent != nil {
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
}
fmt.Fprintf(buf, "PPid:\t%d\n", ppid)
tpid := kernel.ThreadID(0)
if tracer := s.t.Tracer(); tracer != nil {
tpid = s.pidns.IDOfTask(tracer)
}
fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid)
var fds int
var vss, rss, data uint64
s.t.WithMuLocked(func(t *kernel.Task) {
if fdTable := t.FDTable(); fdTable != nil {
fds = fdTable.Size()
}
if mm := t.MemoryManager(); mm != nil {
vss = mm.VirtualMemorySize()
rss = mm.ResidentSetSize()
data = mm.VirtualDataSize()
}
})
fmt.Fprintf(buf, "FDSize:\t%d\n", fds)
fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10)
fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10)
fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10)
fmt.Fprintf(buf, "Threads:\t%d\n", s.t.ThreadGroup().Count())
creds := s.t.Credentials()
fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps)
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
fmt.Fprintf(buf, "Seccomp:\t%d\n", s.t.SeccompMode())
return nil
}
// ioUsage is the /proc/<pid>/io and /proc/<pid>/task/<tid>/io data provider.
type ioUsage interface {
// IOUsage returns the io usage data.
IOUsage() *usage.IO
}
// +stateify savable
type ioData struct {
ioUsage
}
var _ vfs.DynamicBytesSource = (*ioData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (i *ioData) Generate(ctx context.Context, buf *bytes.Buffer) error {
io := usage.IO{}
io.Accumulate(i.IOUsage())
fmt.Fprintf(buf, "char: %d\n", io.CharsRead)
fmt.Fprintf(buf, "wchar: %d\n", io.CharsWritten)
fmt.Fprintf(buf, "syscr: %d\n", io.ReadSyscalls)
fmt.Fprintf(buf, "syscw: %d\n", io.WriteSyscalls)
fmt.Fprintf(buf, "read_bytes: %d\n", io.BytesRead)
fmt.Fprintf(buf, "write_bytes: %d\n", io.BytesWritten)
fmt.Fprintf(buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled)
return nil
}

View File

@ -0,0 +1,68 @@
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// versionData implements vfs.DynamicBytesSource for /proc/version.
//
// +stateify savable
type versionData struct {
// k is the owning Kernel.
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*versionData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
init := v.k.GlobalInit()
if init == nil {
// Attempted to read before the init Task is created. This can
// only occur during startup, which should never need to read
// this file.
panic("Attempted to read version before initial Task is available")
}
// /proc/version takes the form:
//
// "SYSNAME version RELEASE (COMPILE_USER@COMPILE_HOST)
// (COMPILER_VERSION) VERSION"
//
// where:
// - SYSNAME, RELEASE, and VERSION are the same as returned by
// sys_utsname
// - COMPILE_USER is the user that build the kernel
// - COMPILE_HOST is the hostname of the machine on which the kernel
// was built
// - COMPILER_VERSION is the version reported by the building compiler
//
// Since we don't really want to expose build information to
// applications, those fields are omitted.
//
// FIXME(mpratt): Using Version from the init task SyscallTable
// disregards the different version a task may have (e.g., in a uts
// namespace).
ver := init.Leader().SyscallTable().Version
fmt.Fprintf(buf, "%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)
return nil
}

View File

@ -58,6 +58,34 @@ func (mm *MemoryManager) NeedsUpdate(generation int64) bool {
return true
}
// ReadMapsDataInto is called by fsimpl/proc.mapsData.Generate to
// implement /proc/[pid]/maps.
func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer) {
mm.mappingMu.RLock()
defer mm.mappingMu.RUnlock()
var start usermem.Addr
for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() {
// FIXME(b/30793614): If we use a usermem.Addr for the handle, we get
// "panic: autosave error: type usermem.Addr is not registered".
mm.appendVMAMapsEntryLocked(ctx, vseg, buf)
}
// We always emulate vsyscall, so advertise it here. Everything about a
// vsyscall region is static, so just hard code the maps entry since we
// don't have a real vma backing it. The vsyscall region is at the end of
// the virtual address space so nothing should be mapped after it (if
// something is really mapped in the tiny ~10 MiB segment afterwards, we'll
// get the sorting on the maps file wrong at worst; but that's not possible
// on any current platform).
//
// Artifically adjust the seqfile handle so we only output vsyscall entry once.
if start != vsyscallEnd {
// FIXME(b/30793614): Can't get a pointer to constant vsyscallEnd.
buf.WriteString(vsyscallMapsEntry)
}
}
// ReadMapsSeqFileData is called by fs/proc.mapsData.ReadSeqFileData to
// implement /proc/[pid]/maps.
func (mm *MemoryManager) ReadMapsSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
@ -151,6 +179,27 @@ func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaI
b.WriteString("\n")
}
// ReadSmapsDataInto is called by fsimpl/proc.smapsData.Generate to
// implement /proc/[pid]/maps.
func (mm *MemoryManager) ReadSmapsDataInto(ctx context.Context, buf *bytes.Buffer) {
mm.mappingMu.RLock()
defer mm.mappingMu.RUnlock()
var start usermem.Addr
for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() {
// FIXME(b/30793614): If we use a usermem.Addr for the handle, we get
// "panic: autosave error: type usermem.Addr is not registered".
mm.vmaSmapsEntryIntoLocked(ctx, vseg, buf)
}
// We always emulate vsyscall, so advertise it here. See
// ReadMapsSeqFileData for additional commentary.
if start != vsyscallEnd {
// FIXME(b/30793614): Can't get a pointer to constant vsyscallEnd.
buf.WriteString(vsyscallSmapsEntry)
}
}
// ReadSmapsSeqFileData is called by fs/proc.smapsData.ReadSeqFileData to
// implement /proc/[pid]/smaps.
func (mm *MemoryManager) ReadSmapsSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
@ -190,7 +239,12 @@ func (mm *MemoryManager) ReadSmapsSeqFileData(ctx context.Context, handle seqfil
// Preconditions: mm.mappingMu must be locked.
func (mm *MemoryManager) vmaSmapsEntryLocked(ctx context.Context, vseg vmaIterator) []byte {
var b bytes.Buffer
mm.appendVMAMapsEntryLocked(ctx, vseg, &b)
mm.vmaSmapsEntryIntoLocked(ctx, vseg, &b)
return b.Bytes()
}
func (mm *MemoryManager) vmaSmapsEntryIntoLocked(ctx context.Context, vseg vmaIterator, b *bytes.Buffer) {
mm.appendVMAMapsEntryLocked(ctx, vseg, b)
vma := vseg.ValuePtr()
// We take mm.activeMu here in each call to vmaSmapsEntryLocked, instead of
@ -211,40 +265,40 @@ func (mm *MemoryManager) vmaSmapsEntryLocked(ctx context.Context, vseg vmaIterat
}
mm.activeMu.RUnlock()
fmt.Fprintf(&b, "Size: %8d kB\n", vseg.Range().Length()/1024)
fmt.Fprintf(&b, "Rss: %8d kB\n", rss/1024)
fmt.Fprintf(b, "Size: %8d kB\n", vseg.Range().Length()/1024)
fmt.Fprintf(b, "Rss: %8d kB\n", rss/1024)
// Currently we report PSS = RSS, i.e. we pretend each page mapped by a pma
// is only mapped by that pma. This avoids having to query memmap.Mappables
// for reference count information on each page. As a corollary, all pages
// are accounted as "private" whether or not the vma is private; compare
// Linux's fs/proc/task_mmu.c:smaps_account().
fmt.Fprintf(&b, "Pss: %8d kB\n", rss/1024)
fmt.Fprintf(&b, "Shared_Clean: %8d kB\n", 0)
fmt.Fprintf(&b, "Shared_Dirty: %8d kB\n", 0)
fmt.Fprintf(b, "Pss: %8d kB\n", rss/1024)
fmt.Fprintf(b, "Shared_Clean: %8d kB\n", 0)
fmt.Fprintf(b, "Shared_Dirty: %8d kB\n", 0)
// Pretend that all pages are dirty if the vma is writable, and clean otherwise.
clean := rss
if vma.effectivePerms.Write {
clean = 0
}
fmt.Fprintf(&b, "Private_Clean: %8d kB\n", clean/1024)
fmt.Fprintf(&b, "Private_Dirty: %8d kB\n", (rss-clean)/1024)
fmt.Fprintf(b, "Private_Clean: %8d kB\n", clean/1024)
fmt.Fprintf(b, "Private_Dirty: %8d kB\n", (rss-clean)/1024)
// Pretend that all pages are "referenced" (recently touched).
fmt.Fprintf(&b, "Referenced: %8d kB\n", rss/1024)
fmt.Fprintf(&b, "Anonymous: %8d kB\n", anon/1024)
fmt.Fprintf(b, "Referenced: %8d kB\n", rss/1024)
fmt.Fprintf(b, "Anonymous: %8d kB\n", anon/1024)
// Hugepages (hugetlb and THP) are not implemented.
fmt.Fprintf(&b, "AnonHugePages: %8d kB\n", 0)
fmt.Fprintf(&b, "Shared_Hugetlb: %8d kB\n", 0)
fmt.Fprintf(&b, "Private_Hugetlb: %7d kB\n", 0)
fmt.Fprintf(b, "AnonHugePages: %8d kB\n", 0)
fmt.Fprintf(b, "Shared_Hugetlb: %8d kB\n", 0)
fmt.Fprintf(b, "Private_Hugetlb: %7d kB\n", 0)
// Swap is not implemented.
fmt.Fprintf(&b, "Swap: %8d kB\n", 0)
fmt.Fprintf(&b, "SwapPss: %8d kB\n", 0)
fmt.Fprintf(&b, "KernelPageSize: %8d kB\n", usermem.PageSize/1024)
fmt.Fprintf(&b, "MMUPageSize: %8d kB\n", usermem.PageSize/1024)
fmt.Fprintf(b, "Swap: %8d kB\n", 0)
fmt.Fprintf(b, "SwapPss: %8d kB\n", 0)
fmt.Fprintf(b, "KernelPageSize: %8d kB\n", usermem.PageSize/1024)
fmt.Fprintf(b, "MMUPageSize: %8d kB\n", usermem.PageSize/1024)
locked := rss
if vma.mlockMode == memmap.MLockNone {
locked = 0
}
fmt.Fprintf(&b, "Locked: %8d kB\n", locked/1024)
fmt.Fprintf(b, "Locked: %8d kB\n", locked/1024)
b.WriteString("VmFlags: ")
if vma.realPerms.Read {
@ -284,6 +338,4 @@ func (mm *MemoryManager) vmaSmapsEntryLocked(ctx context.Context, vseg vmaIterat
b.WriteString("ac ")
}
b.WriteString("\n")
return b.Bytes()
}