summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys-utils/chmem.819
-rw-r--r--sys-utils/chmem.c136
-rw-r--r--sys-utils/lsmem.14
-rw-r--r--sys-utils/lsmem.c98
4 files changed, 246 insertions, 11 deletions
diff --git a/sys-utils/chmem.8 b/sys-utils/chmem.8
index a116bc9e7..dae7413d4 100644
--- a/sys-utils/chmem.8
+++ b/sys-utils/chmem.8
@@ -5,6 +5,7 @@ chmem \- configure memory
.B chmem
.RB [ \-h "] [" \-V "] [" \-v "] [" \-e | \-d "]"
[\fISIZE\fP|\fIRANGE\fP|\fB\-b\fP \fIBLOCKRANGE\fP]
+[-z ZONE]
.SH DESCRIPTION
The chmem command sets a particular size or range of memory online or offline.
.
@@ -25,6 +26,19 @@ and <last> is the number of the last memory block in the memory
range. Alternatively a single block can be specified. \fIBLOCKRANGE\fP requires
the \fB--blocks\fP option.
.
+.IP "\(hy" 2
+Specify \fIZONE\fP as the name of a memory zone, as shown in the output of the
+\fBlsmem -o +ZONES\fP command. The output shows one or more valid memory zones
+for each memory range. If multiple zones are shown, then the memory range
+currently belongs to the first zone. By default, chmem will set memory online
+to the zone Movable, if this is among the valid zones. This default can be
+changed by specifying the \fB--zone\fP option with another valid zone.
+For memory ballooning, it is recommended to select the zone Movable for memory
+online and offline, if possible. Memory in this zone is much more likely to be
+able to be offlined again, but it cannot be used for arbitrary kernel
+allocations, only for migratable pages (e.g. anonymous and page cache pages).
+Use the \fB\-\-help\fR option to see all available zones.
+.
.PP
\fISIZE\fP and \fIRANGE\fP must be aligned to the Linux memory block size, as
shown in the output of the \fBlsmem\fP command.
@@ -51,6 +65,11 @@ Set the specified \fIRANGE\fP, \fISIZE\fP, or \fIBLOCKRANGE\fP of memory offline
.BR \-e ", " \-\-enable
Set the specified \fIRANGE\fP, \fISIZE\fP, or \fIBLOCKRANGE\fP of memory online.
.TP
+.BR \-z ", " \-\-zone
+Select the memory \fIZONE\fP where to set the specified \fIRANGE\fP, \fISIZE\fP,
+or \fIBLOCKRANGE\fP of memory online or offline. By default, memory will be set
+online to the zone Movable, if possible.
+.TP
.BR \-h ", " \-\-help
Print a short help text, then exit.
.TP
diff --git a/sys-utils/chmem.c b/sys-utils/chmem.c
index d9bc95cc1..2f0680de8 100644
--- a/sys-utils/chmem.c
+++ b/sys-utils/chmem.c
@@ -49,6 +49,7 @@ struct chmem_desc {
unsigned int use_blocks : 1;
unsigned int is_size : 1;
unsigned int verbose : 1;
+ unsigned int have_zones : 1;
};
enum {
@@ -57,6 +58,38 @@ enum {
CMD_NONE
};
+enum zone_id {
+ ZONE_DMA = 0,
+ ZONE_DMA32,
+ ZONE_NORMAL,
+ ZONE_HIGHMEM,
+ ZONE_MOVABLE,
+ ZONE_DEVICE,
+};
+
+static char *zone_names[] = {
+ [ZONE_DMA] = "DMA",
+ [ZONE_DMA32] = "DMA32",
+ [ZONE_NORMAL] = "Normal",
+ [ZONE_HIGHMEM] = "Highmem",
+ [ZONE_MOVABLE] = "Movable",
+ [ZONE_DEVICE] = "Device",
+};
+
+/*
+ * name must be null-terminated
+ */
+static int zone_name_to_id(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
+ if (!strcasecmp(name, zone_names[i]))
+ return i;
+ }
+ return -1;
+}
+
static void idxtostr(struct chmem_desc *desc, uint64_t idx, char *buf, size_t bufsz)
{
uint64_t start, end;
@@ -68,22 +101,49 @@ static void idxtostr(struct chmem_desc *desc, uint64_t idx, char *buf, size_t bu
idx, start, end);
}
-static int chmem_size(struct chmem_desc *desc, int enable)
+static int chmem_size(struct chmem_desc *desc, int enable, int zone_id)
{
char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
uint64_t size, index;
+ const char *zn;
int i, rc;
size = desc->size;
onoff = enable ? "online" : "offline";
i = enable ? 0 : desc->ndirs - 1;
+ if (enable && zone_id >= 0) {
+ if (zone_id == ZONE_MOVABLE)
+ onoff = "online_movable";
+ else
+ onoff = "online_kernel";
+ }
+
for (; i >= 0 && i < desc->ndirs && size; i += enable ? 1 : -1) {
name = desc->dirs[i]->d_name;
index = strtou64_or_err(name + 6, _("Failed to parse index"));
path_read_str(line, sizeof(line), _PATH_SYS_MEMORY "/%s/state", name);
- if (strcmp(onoff, line) == 0)
+ if (strncmp(onoff, line, 6) == 0)
continue;
+
+ if (desc->have_zones) {
+ path_read_str(line, sizeof(line),
+ _PATH_SYS_MEMORY "/%s/valid_zones", name);
+ if (zone_id >= 0) {
+ zn = zone_names[zone_id];
+ if (enable && !strcasestr(line, zn))
+ continue;
+ if (!enable && strncasecmp(line, zn, strlen(zn)))
+ continue;
+ } else if (enable) {
+ /* By default, use zone Movable for online, if valid */
+ if (strcasestr(line, zone_names[ZONE_MOVABLE]))
+ onoff = "online_movable";
+ else
+ onoff = "online";
+ }
+ }
+
idxtostr(desc, index, str, sizeof(str));
rc = path_write_str(onoff, _PATH_SYS_MEMORY"/%s/state", name);
if (rc == -1 && desc->verbose) {
@@ -115,15 +175,23 @@ static int chmem_size(struct chmem_desc *desc, int enable)
return size == 0 ? 0 : size == desc->size ? -1 : 1;
}
-static int chmem_range(struct chmem_desc *desc, int enable)
+static int chmem_range(struct chmem_desc *desc, int enable, int zone_id)
{
char *name, *onoff, line[BUFSIZ], str[BUFSIZ];
uint64_t index, todo;
+ const char *zn;
int i, rc;
todo = desc->end - desc->start + 1;
onoff = enable ? "online" : "offline";
+ if (enable && zone_id >= 0) {
+ if (zone_id == ZONE_MOVABLE)
+ onoff = "online_movable";
+ else
+ onoff = "online_kernel";
+ }
+
for (i = 0; i < desc->ndirs; i++) {
name = desc->dirs[i]->d_name;
index = strtou64_or_err(name + 6, _("Failed to parse index"));
@@ -133,7 +201,7 @@ static int chmem_range(struct chmem_desc *desc, int enable)
break;
idxtostr(desc, index, str, sizeof(str));
path_read_str(line, sizeof(line), _PATH_SYS_MEMORY "/%s/state", name);
- if (strcmp(onoff, line) == 0) {
+ if (strncmp(onoff, line, 6) == 0) {
if (desc->verbose && enable)
fprintf(stdout, _("%s already enabled\n"), str);
else if (desc->verbose && !enable)
@@ -141,6 +209,29 @@ static int chmem_range(struct chmem_desc *desc, int enable)
todo--;
continue;
}
+
+ if (desc->have_zones) {
+ path_read_str(line, sizeof(line),
+ _PATH_SYS_MEMORY "/%s/valid_zones", name);
+ if (zone_id >= 0) {
+ zn = zone_names[zone_id];
+ if (enable && !strcasestr(line, zn)) {
+ warnx(_("%s enable failed: Zone mismatch"), str);
+ continue;
+ }
+ if (!enable && strncasecmp(line, zn, strlen(zn))) {
+ warnx(_("%s disable failed: Zone mismatch"), str);
+ continue;
+ }
+ } else if (enable) {
+ /* By default, use zone Movable for online, if valid */
+ if (strcasestr(line, zone_names[ZONE_MOVABLE]))
+ onoff = "online_movable";
+ else
+ onoff = "online";
+ }
+ }
+
rc = path_write_str(onoff, _PATH_SYS_MEMORY"/%s/state", name);
if (rc == -1) {
if (enable)
@@ -237,6 +328,8 @@ static void parse_parameter(struct chmem_desc *desc, char *param)
static void __attribute__((__noreturn__)) usage(void)
{
FILE *out = stdout;
+ unsigned int i;
+
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] [SIZE|RANGE|BLOCKRANGE]\n"), program_invocation_short_name);
@@ -247,6 +340,14 @@ static void __attribute__((__noreturn__)) usage(void)
fputs(_(" -e, --enable enable memory\n"), out);
fputs(_(" -d, --disable disable memory\n"), out);
fputs(_(" -b, --blocks use memory blocks\n"), out);
+ fputs(_(" -z, --zone select memory zone ("), out);
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
+ fputs(zone_names[i], out);
+ if (i < ARRAY_SIZE(zone_names) - 1)
+ fputc('|', out);
+ }
+ fputs(")\n", out);
+
fputs(_(" -v, --verbose verbose output\n"), out);
fputs(USAGE_SEPARATOR, out);
printf(USAGE_HELP_OPTIONS(16));
@@ -259,7 +360,8 @@ static void __attribute__((__noreturn__)) usage(void)
int main(int argc, char **argv)
{
struct chmem_desc _desc = { }, *desc = &_desc;
- int cmd = CMD_NONE;
+ int cmd = CMD_NONE, zone_id = -1;
+ char *zone = NULL;
int c, rc;
static const struct option longopts[] = {
@@ -269,6 +371,7 @@ int main(int argc, char **argv)
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
+ {"zone", required_argument, NULL, 'z'},
{NULL, 0, NULL, 0}
};
@@ -285,7 +388,7 @@ int main(int argc, char **argv)
read_info(desc);
- while ((c = getopt_long(argc, argv, "bdehvV", longopts, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "bdehvVz:", longopts, NULL)) != -1) {
err_exclusive_options(c, longopts, excl, excl_st);
@@ -308,6 +411,9 @@ int main(int argc, char **argv)
case 'V':
printf(UTIL_LINUX_VERSION);
return EXIT_SUCCESS;
+ case 'z':
+ zone = xstrdup(optarg);
+ break;
default:
errtryhelp(EXIT_FAILURE);
}
@@ -320,10 +426,24 @@ int main(int argc, char **argv)
parse_parameter(desc, argv[optind]);
+ /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
+ if (path_exist(_PATH_SYS_MEMORY "/memory0/valid_zones"))
+ desc->have_zones = 1;
+ else if (zone)
+ warnx(_("zone ignored, no valid_zones sysfs attribute present"));
+
+ if (zone && desc->have_zones) {
+ zone_id = zone_name_to_id(zone);
+ if (zone_id == -1) {
+ warnx(_("unknown memory zone: %s"), zone);
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
if (desc->is_size)
- rc = chmem_size(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0);
+ rc = chmem_size(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
else
- rc = chmem_range(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0);
+ rc = chmem_range(desc, cmd == CMD_MEMORY_ENABLE ? 1 : 0, zone_id);
return rc == 0 ? EXIT_SUCCESS :
rc < 0 ? EXIT_FAILURE : CHMEM_EXIT_SOMEOK;
diff --git a/sys-utils/lsmem.1 b/sys-utils/lsmem.1
index be2862d94..e7df50a4e 100644
--- a/sys-utils/lsmem.1
+++ b/sys-utils/lsmem.1
@@ -41,12 +41,12 @@ Do not print a header line.
.BR \-o , " \-\-output " \fIlist\fP
Specify which output columns to print. Use \fB\-\-help\fR
to get a list of all supported columns.
+The default list of columns may be extended if \fIlist\fP is
+specified in the format \fB+\fIlist\fP (e.g. \fBlsmem \-o +NODE\fP).
.TP
.BR \-P , " \-\-pairs"
Produce output in the form of key="value" pairs.
All potentially unsafe characters are hex-escaped (\\x<code>).
-The default list of columns may be extended if \fIlist\fP is
-specified in the format \fB+\fIlist\fP (e.g. \fBlsmem \-o +NODE\fP).
.TP
.BR \-r , " \-\-raw"
Produce output in raw format. All potentially unsafe characters are hex-escaped
diff --git a/sys-utils/lsmem.c b/sys-utils/lsmem.c
index aeffd29dd..1d26579fd 100644
--- a/sys-utils/lsmem.c
+++ b/sys-utils/lsmem.c
@@ -42,11 +42,25 @@
#define MEMORY_STATE_GOING_OFFLINE 2
#define MEMORY_STATE_UNKNOWN 3
+enum zone_id {
+ ZONE_DMA = 0,
+ ZONE_DMA32,
+ ZONE_NORMAL,
+ ZONE_HIGHMEM,
+ ZONE_MOVABLE,
+ ZONE_DEVICE,
+ ZONE_NONE,
+ ZONE_UNKNOWN,
+ MAX_NR_ZONES,
+};
+
struct memory_block {
uint64_t index;
uint64_t count;
int state;
int node;
+ int nr_zones;
+ int zones[MAX_NR_ZONES];
unsigned int removable:1;
};
@@ -72,7 +86,9 @@ struct lsmem {
want_state : 1,
want_removable : 1,
want_summary : 1,
- want_table : 1;
+ want_table : 1,
+ want_zones : 1,
+ have_zones : 1;
};
enum {
@@ -82,6 +98,18 @@ enum {
COL_REMOVABLE,
COL_BLOCK,
COL_NODE,
+ COL_ZONES,
+};
+
+static char *zone_names[] = {
+ [ZONE_DMA] = "DMA",
+ [ZONE_DMA32] = "DMA32",
+ [ZONE_NORMAL] = "Normal",
+ [ZONE_HIGHMEM] = "Highmem",
+ [ZONE_MOVABLE] = "Movable",
+ [ZONE_DEVICE] = "Device",
+ [ZONE_NONE] = "None", /* block contains more than one zone, can't be offlined */
+ [ZONE_UNKNOWN] = "Unknown",
};
/* column names */
@@ -102,6 +130,7 @@ static struct coldesc coldescs[] = {
[COL_REMOVABLE] = { "REMOVABLE", 0, SCOLS_FL_RIGHT, N_("memory is removable")},
[COL_BLOCK] = { "BLOCK", 0, SCOLS_FL_RIGHT, N_("memory block number or blocks range")},
[COL_NODE] = { "NODE", 0, SCOLS_FL_RIGHT, N_("numa node of memory")},
+ [COL_ZONES] = { "ZONES", 0, SCOLS_FL_RIGHT, N_("valid zones for the memory range")},
};
/* columns[] array specifies all currently wanted output column. The columns
@@ -120,6 +149,20 @@ static inline size_t err_columns_index(size_t arysz, size_t idx)
return idx;
}
+/*
+ * name must be null-terminated
+ */
+static int zone_name_to_id(const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(zone_names); i++) {
+ if (!strcasecmp(name, zone_names[i]))
+ return i;
+ }
+ return ZONE_UNKNOWN;
+}
+
#define add_column(ary, n, id) \
((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
@@ -214,6 +257,25 @@ static void add_scols_line(struct lsmem *lsmem, struct memory_block *blk)
else
str = xstrdup("-");
break;
+ case COL_ZONES:
+ if (lsmem->have_zones) {
+ char valid_zones[BUFSIZ];
+ int j, zone_id;
+
+ valid_zones[0] = '\0';
+ for (j = 0; j < blk->nr_zones; j++) {
+ zone_id = blk->zones[j];
+ if (strlen(valid_zones) +
+ strlen(zone_names[zone_id]) > BUFSIZ - 2)
+ break;
+ strcat(valid_zones, zone_names[zone_id]);
+ if (j + 1 < blk->nr_zones)
+ strcat(valid_zones, "/");
+ }
+ str = xstrdup(valid_zones);
+ } else
+ str = xstrdup("-");
+ break;
}
if (str && scols_line_refer_data(line, i, str) != 0)
@@ -272,7 +334,9 @@ static int memory_block_get_node(char *name)
static void memory_block_read_attrs(struct lsmem *lsmem, char *name,
struct memory_block *blk)
{
+ char *token = NULL;
char line[BUFSIZ];
+ int i;
blk->count = 1;
blk->index = strtoumax(name + 6, NULL, 10); /* get <num> of "memory<num>" */
@@ -287,11 +351,26 @@ static void memory_block_read_attrs(struct lsmem *lsmem, char *name,
blk->state = MEMORY_STATE_GOING_OFFLINE;
if (lsmem->have_nodes)
blk->node = memory_block_get_node(name);
+
+ blk->nr_zones = 0;
+ if (lsmem->have_zones) {
+ path_read_str(line, sizeof(line), _PATH_SYS_MEMORY"/%s/%s", name,
+ "valid_zones");
+ token = strtok(line, " ");
+ }
+ for (i = 0; i < MAX_NR_ZONES; i++) {
+ if (token) {
+ blk->zones[i] = zone_name_to_id(token);
+ blk->nr_zones++;
+ token = strtok(NULL, " ");
+ }
+ }
}
static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk)
{
struct memory_block *curr;
+ int i;
if (!lsmem->nblocks)
return 0;
@@ -308,6 +387,15 @@ static int is_mergeable(struct lsmem *lsmem, struct memory_block *blk)
if (curr->node != blk->node)
return 0;
}
+ if (lsmem->want_zones && lsmem->have_zones) {
+ if (curr->nr_zones != blk->nr_zones)
+ return 0;
+ for (i = 0; i < curr->nr_zones; i++) {
+ if (curr->zones[i] == ZONE_UNKNOWN ||
+ curr->zones[i] != blk->zones[i])
+ return 0;
+ }
+ }
return 1;
}
@@ -362,6 +450,12 @@ static void read_basic_info(struct lsmem *lsmem)
if (memory_block_get_node(lsmem->dirs[0]->d_name) != -1)
lsmem->have_nodes = 1;
+
+ /* The valid_zones sysfs attribute was introduced with kernel 3.18 */
+ if (path_exist(_PATH_SYS_MEMORY "/memory0/valid_zones"))
+ lsmem->have_zones = 1;
+ else if (lsmem->want_zones)
+ warnx(_("Cannot read zones, no valid_zones sysfs attribute present"));
}
static void __attribute__((__noreturn__)) usage(void)
@@ -553,6 +647,8 @@ int main(int argc, char **argv)
lsmem->want_node = 1;
if (has_column(COL_REMOVABLE))
lsmem->want_removable = 1;
+ if (has_column(COL_ZONES))
+ lsmem->want_zones = 1;
/*
* Read data and print output