yaobin.wen

Yaobin's Blog

View on GitHub
20 December 2024

How to get the total physical RAM on Ubuntu: sounds easy, but not really

by yaobin.wen

1. Sounds like an easy task.

Today, I needed to implement a small piece of code to get the total amount of the physical RAM counted in GB on my machine. A quick Google search told me that I could get it by reading the field MemTotal in the file /proc/meminfo:

MemTotal:       65797108 kB
MemFree:        45274684 kB
MemAvailable:   60898056 kB
...

A simple awk command can do it:

$ awk '/MemTotal/ {printf "%.0f\n", $2 / (1024 * 1024)}' /proc/meminfo

In this awk, $2 was the number 65797108 in the unit of kB, thus (1024 * 1024) converted it to GB. %.0f made sure the decimal part of the result would be rounded up or down to the nearest integer. The result was 63 which was close to the actual 64 GB of physical RAM on my machine. Sounds an easy task, right? But as I investigated further, this project turned out to be more complicated than I thought.

2. Isn’t kB to the powers of 10?

I noticed that the units in /proc/meminfo were all kB. According to Wikipedia, kB represents 1000 bytes:

The International System of Units (SI) defines the prefix kilo as a multiplication factor of 1000 ($10^3$); therefore, one kilobyte is 1000 bytes.

If this is true, then I would have used (1000 * 1000) to convert the MemTotal value to GB. However, when I checked the result from free, I saw free reported the same amount of total memory:

$ free 
              total        used        free      shared  buff/cache   available
Mem:       65797108     5945032    42368604       90780    17483472    59129968
Swap:       2097148           0     2097148

And according to free(1), everything is reported in kibibytes by default:

   -k, --kibi
        Display the amount of memory in kibibytes.  This is the default.

Therefore, (1024 * 1024) was the right conversion to use. But I still wanted to confirm that /proc/meminfo uses kibibytes as the unit.

This post looks into the kernel code to confirm that /proc/meminfo reports everything in kibibytes even if the units were written in kB:

… the kernel internally counts memory in terms of free pages (which are typically 4k or 16k but always power-of-two) and its show_val_kb() function uses a bit shift operation (which is equivalent to a multiplication by power-of-two, producing binary units again) to convert the page count into a kilobyte value:

static void show_val_kb(struct seq_file *m, const char *s, unsigned long num)
{
        seq_put_decimal_ull_width(m, s, num << (PAGE_SHIFT - 10), 8);
        seq_write(m, " kB\n", 4);
}

The function show_val_kb() can be found in linux/fs/proc/meminfo.c.

In fact, there was an email thread “[RFC] proc: meminfo: Replace kB with KiB in output” that asked “the reason what the output is as it is.” Then Alexandru Juncu submitted a patch in the email “[PATCH] proc: meminfo: Replace kB with KiB in output” to fix it. However, Andy Shevchenko replied and said:

I’m pretty sure you will get NAK for that. Obvious reason — procfs is ABI of the kernel. “We won’t break userspace.”

The conclusion is: MemTotal (and all the other fields in /proc/meminfo) was in the unit of kibibytes even though the output says kB. It remained unchanged in order to maintain backward compatibility to avoid breaking userspace programs.

3. Where is my 64th GiB of RAM?

The awk calculation result was 63 GiB. However, I know my machine had 64 GiB of RAM. Where is the 64th GiB?

It turned out to be that MemTotal, per proc(5), is only the total usable RAM:

          MemTotal %lu
               Total usable RAM (i.e., physical RAM minus a few reserved bits and the kernel binary code).

So the discrepancy between MemTotal and the actual physical RAM was caused by the RAM that kernel reserves. So my question became: What can tell me either the total amount of the physical RAM or the amount of RAM that kernel reserves?

This Stack Overflow post How can I find out the total physical memory (RAM) of my linux box suitable to be parsed by a shell script? provides a lot of ideas.

3.1 Use dmidecode

sudo dmidecode -t memory can list all the available RAM:

# dmidecode 3.1
Getting SMBIOS data from sysfs.
SMBIOS 3.0.0 present.

Handle 0x0048, DMI type 16, 23 bytes
Physical Memory Array
	Location: System Board Or Motherboard
	Use: System Memory
	Error Correction Type: None
	Maximum Capacity: 64 GB
	Error Information Handle: Not Provided
	Number Of Devices: 4

Handle 0x0049, DMI type 17, 40 bytes
Memory Device
	...
	Size: 16384 MB
	...

Handle 0x004A, DMI type 17, 40 bytes
Memory Device
	...
	Size: 16384 MB
	...

Handle 0x004B, DMI type 17, 40 bytes
Memory Device
	...
	Size: 16384 MB
	...

Handle 0x004C, DMI type 17, 40 bytes
Memory Device
	...
	Size: 16384 MB
  ...

We should skip “Physical Memory Array” because that is for the available memory slots, not the actual physical memory installed.

We can use the Size field in the Memory Device entries to calculate the total installed physical memory. For my machine, the total physical memory is $16384 MB + 16384 MB + 16384 MB + 16384 MB = 65536 MB = 64 GiB$. (dmidecode reports sizes to the powers of 2.)

Unfortunately, dmidecode doesn’t seem to support reporting results in any machine-readable format. People have been asking for that (see the email thread [dmidecode] Question: YAML and/or JSON output support), but the development team said no. However, Kelly Brazil implemented a tool called jc that “converts the output of popular command-line tools, file-types, and common strings to JSON, YAML, or Dictionaries.” I haven’t checked it out by myself, but it looks like a powerful tool. Another reason I didn’t check it out was because the lsmem tool that I’m going to talk about below can report the total physical RAM in the machine-readable format JSON.

3.2. Use online memory blocks under /sys/devices/system/memory

This answer suggests using the online memory blocks to calculate the total physical memory.

totalmem=0;
for mem in /sys/devices/system/memory/memory*; do
  [[ "$(cat ${mem}/online)" == "1" ]] \
    && totalmem=$((totalmem+$((0x$(cat /sys/devices/system/memory/block_size_bytes)))));
done

echo ${totalmem} bytes
echo $((totalmem/1024**3)) GB

This is the most accurate way to calculate the total amount of the physical RAM. Unfortunately, it’s probably also the slowest method because it needs to traverse all the memory block files on the file system. On my Ubuntu system, it could take up to 1 second (wall-clock time) to finish the calculation:

$ time ./calc-phy-ram.sh
68719476736 bytes
64 GB

real	0m1.025s
user	0m0.812s
sys	0m0.275s

3.3 Use lsmem

This answer uses lsmem to figure out the total amount of the physical RAM:

lsmem -b --summary=only | sed -ne '/online/s/.* //p'

In fact, using lsmem seems to be the same as using the online memory blocks. It can directly show the total online memory:

$ lsmem
RANGE                                 SIZE  STATE REMOVABLE  BLOCK
0x0000000000000000-0x000000007fffffff   2G online       yes   0-15
0x0000000100000000-0x000000107fffffff  62G online       yes 32-527

Memory block size:       128M
Total online memory:      64G
Total offline memory:      0B

Interestingly, lsmem can return the result almost instantly, whereas using the online memory blocks will take almost one second. lsmem is part of the package util-linux and the source code can be found in util-linux/util-linux. lsmem is implemented by the source file lsmem.c. The function void read_info(struct lsmem *lsmem) seems to be the one that reads all the online memory blocks and adds up the sizes. The code from the version 2.39 (used on Ubuntu Noble) is quoted as follows (see here):

static void read_info(struct lsmem *lsmem)
{
	struct memory_block blk;
	char buf[128];
	int i;

	if (ul_path_read_buffer(lsmem->sysmem, buf, sizeof(buf), "block_size_bytes") <= 0)
		err(EXIT_FAILURE, _("failed to read memory block size"));

	errno = 0;
	lsmem->block_size = strtoumax(buf, NULL, 16);
	if (errno)
		err(EXIT_FAILURE, _("failed to read memory block size"));

	for (i = 0; i < lsmem->ndirs; i++) {
		memory_block_read_attrs(lsmem, lsmem->dirs[i]->d_name, &blk);
		if (blk.state == MEMORY_STATE_ONLINE)
			lsmem->mem_online += lsmem->block_size;
		else
			lsmem->mem_offline += lsmem->block_size;
		if (is_mergeable(lsmem, &blk)) {
			lsmem->blocks[lsmem->nblocks - 1].count++;
			continue;
		}
		lsmem->nblocks++;
		lsmem->blocks = xrealloc(lsmem->blocks, lsmem->nblocks * sizeof(blk));
		*&lsmem->blocks[lsmem->nblocks - 1] = blk;
	}
}

It looks like the function read_info() doesn’t do any magic but also simply loops through all the memory blocks and adds up their sizes. I haven’t studied further, but I guess the shell script in the section 3.2 needs to start a lot of child processes to perform the file access and the starting/stopping of the child processes probably adds up a lot of overhead.

3.4 Use DirectMapX in /proc/meminfo

This answer mentions the use of the DirectMapX entries in /proc/meminfo: Simply add the values up and convert the sum to the desired unit. On my machine, there are three DirectMapX entries:

DirectMap4k:      699496 kB
DirectMap2M:    26497024 kB
DirectMap1G:    39845888 kB

So the math is as follows:

However, there is the question that whether all the memory has a direct map. In other words, the question is whether there can be a piece of memory that doesn’t appear in any of the DirectMap entry. If there can be such memory, then the sum of all the DirectMap entries is not the total amount of physical memory on the machine. I need further study of how DirectMapX entries are created in order to answer this question. Therefore, I would not choose this method to calculate the total amount of physical RAM. (Besides, the user Goswin von Brederlow also complained in this comment that “this method doesn’t seem to be reliable at all”)

Tags: Tech - ubuntu