procfs: add memory pressure notification support

This is a memory monitoring interface implemented with reference to Linux's PSI (Pressure Stall Information),
which can send notifications when the system's remaining memory is below the threshold.

The following example code sets two different thresholds.
When the system memory is below 10MB, a notification is triggered.
When the system memory is below 20 MB, a notification (POLLPRI event) is triggered every 1s.

```
int main(int argc, FAR char *argv[])
{
  struct pollfd fds[2];
  int ret;

  if (argc == 2)
    {
      char *ptr = malloc(1024*1024*atoi(argv[1]));
      printf("Allocating %d MB\n", atoi(argv[1]));
      ptr[0] = 0;
      return 0;
    }

  fds[0].fd = open("/proc/pressure/memory", O_RDWR);
  fds[1].fd = open("/proc/pressure/memory", O_RDWR);
  fds[0].events = POLLPRI;
  fds[1].events = POLLPRI;

  dprintf(fds[0].fd, "%llu -1", 1024LLU*1024 * 10);
  dprintf(fds[1].fd, "%llu 1000000", 1024LLU*1024 * 20);

  while (1)
    {
      ret = poll(fds, 2, -1);
      if (ret > 0)
        {
          printf("Memory pressure: POLLPRI, %d\n", ret);
        }
    }

  return 0;
}
```

https://docs.kernel.org/accounting/psi.html
Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
This commit is contained in:
yinshengkai 2023-11-29 21:59:49 +08:00 committed by Xiang Xiao
parent fac44ab8aa
commit f44a31c337
5 changed files with 475 additions and 0 deletions

View File

@ -154,5 +154,9 @@ config FS_PROCFS_EXCLUDE_VERSION
bool "Exclude version"
default DEFAULT_SMALL
config FS_PROCFS_INCLUDE_PRESSURE
bool "Include memory pressure notification"
default n
endmenu # Exclude individual procfs entries
endif # FS_PROCFS

View File

@ -26,6 +26,10 @@ CSRCS += fs_procfscritmon.c fs_procfsfdt.c fs_procfsiobinfo.c
CSRCS += fs_procfsmeminfo.c fs_procfsproc.c fs_procfstcbinfo.c
CSRCS += fs_procfsuptime.c fs_procfsutil.c fs_procfsversion.c
ifeq ($(CONFIG_FS_PROCFS_INCLUDE_PRESSURE),y)
CSRCS += fs_procfspressure.c
endif
# Include procfs build support
DEPPATH += --dep-path procfs

View File

@ -68,6 +68,7 @@ extern const struct procfs_operations g_proc_operations;
extern const struct procfs_operations g_tcbinfo_operations;
extern const struct procfs_operations g_uptime_operations;
extern const struct procfs_operations g_version_operations;
extern const struct procfs_operations g_pressure_operations;
/* This is not good. These are implemented in other sub-systems. Having to
* deal with them here is not a good coupling. What is really needed is a
@ -176,6 +177,11 @@ static const struct procfs_entry_s g_procfs_entries[] =
{ "pm/**", &g_pm_operations, PROCFS_UNKOWN_TYPE },
#endif
#ifdef CONFIG_FS_PROCFS_INCLUDE_PRESSURE
{ "pressure", &g_pressure_operations, PROCFS_DIR_TYPE },
{ "pressure/**", &g_pressure_operations, PROCFS_FILE_TYPE },
#endif
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
{ "self", &g_proc_operations, PROCFS_DIR_TYPE },
{ "self/**", &g_proc_operations, PROCFS_UNKOWN_TYPE },

View File

@ -0,0 +1,453 @@
/****************************************************************************
* fs/procfs/fs_procfspressure.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <debug.h>
#include <errno.h>
#include <poll.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <nuttx/fs/procfs.h>
#include <nuttx/fs/fs.h>
#include <nuttx/kmalloc.h>
#include <nuttx/nuttx.h>
#include <nuttx/queue.h>
#include <nuttx/spinlock.h>
/****************************************************************************
* Private Types
****************************************************************************/
struct pressure_file_s
{
struct procfs_file_s base; /* Base open file structure */
dq_entry_t entry; /* Supports a linked list */
FAR struct pollfd *fds; /* Polling structure of waiting thread */
size_t threshold; /* Memory notification threshold */
clock_t lasttick; /* Last time notified */
clock_t interval; /* Notification interval in us */
};
/****************************************************************************
* Private Data
****************************************************************************/
static dq_queue_t g_pressure_memory_queue;
static spinlock_t g_pressure_lock;
static size_t g_remaining;
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int pressure_open(FAR struct file *filep, FAR const char *relpath,
int oflags, mode_t mode);
static int pressure_close(FAR struct file *filep);
static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
static int pressure_dup(FAR const struct file *oldp,
FAR struct file *newp);
static int pressure_opendir(FAR const char *relpath,
FAR struct fs_dirent_s **dir);
static int pressure_closedir(FAR struct fs_dirent_s *dir);
static int pressure_readdir(FAR struct fs_dirent_s *dir,
FAR struct dirent *entry);
static int pressure_rewinddir(FAR struct fs_dirent_s *dir);
static int pressure_stat(FAR const char *relpath, FAR struct stat *buf);
/****************************************************************************
* Public Data
****************************************************************************/
const struct procfs_operations g_pressure_operations =
{
pressure_open, /* open */
pressure_close, /* close */
pressure_read, /* read */
pressure_write, /* write */
pressure_poll, /* poll */
pressure_dup, /* dup */
pressure_opendir, /* opendir */
pressure_closedir, /* closedir */
pressure_readdir, /* readdir */
pressure_rewinddir, /* rewinddir */
pressure_stat /* stat */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pressure_open
****************************************************************************/
static int pressure_open(FAR struct file *filep, FAR const char *relpath,
int oflags, mode_t mode)
{
FAR struct pressure_file_s *priv;
uint32_t flags;
if (strcmp(relpath, "pressure/memory") != 0)
{
ferr("ERROR: relpath is invalid: %s\n", relpath);
return -ENOENT;
}
priv = kmm_zalloc(sizeof(struct pressure_file_s));
if (!priv)
{
return -ENOMEM;
}
flags = spin_lock_irqsave(&g_pressure_lock);
priv->interval = CLOCK_MAX;
filep->f_priv = priv;
dq_addfirst(&priv->entry, &g_pressure_memory_queue);
spin_unlock_irqrestore(&g_pressure_lock, flags);
return OK;
}
/****************************************************************************
* Name: pressure_close
****************************************************************************/
static int pressure_close(FAR struct file *filep)
{
FAR struct pressure_file_s *priv = filep->f_priv;
uint32_t flags;
flags = spin_lock_irqsave(&g_pressure_lock);
dq_rem(&priv->entry, &g_pressure_memory_queue);
spin_unlock_irqrestore(&g_pressure_lock, flags);
free(priv);
return OK;
}
/****************************************************************************
* Name: pressure_read
****************************************************************************/
static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
char buf[128];
uint32_t flags;
ssize_t ret;
flags = spin_lock_irqsave(&g_pressure_lock);
ret = procfs_snprintf(buf, sizeof(buf), "remaining %zu\n",
g_remaining);
spin_unlock_irqrestore(&g_pressure_lock, flags);
if (ret > buflen)
{
return -ENOMEM;
}
memcpy(buffer, buf, ret);
return ret;
}
/****************************************************************************
* Name: pressure_write
****************************************************************************/
static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct pressure_file_s *priv = filep->f_priv;
FAR char *endptr;
size_t threshold;
clock_t interval;
uint32_t flags;
if (buffer == NULL)
{
return -EINVAL;
}
threshold = strtoul(buffer, &endptr, 0);
if (threshold == 0)
{
return -EINVAL;
}
interval = strtol(endptr + 1, NULL, 0);
/* Check if the interval is valid, -1 means only notify once */
if (interval == -1)
{
interval = CLOCK_MAX;
}
else
{
interval = USEC2TICK(interval);
}
flags = spin_lock_irqsave(&g_pressure_lock);
/* We should trigger the first event immediately */
priv->lasttick = CLOCK_MAX;
priv->threshold = threshold;
priv->interval = interval;
spin_unlock_irqrestore(&g_pressure_lock, flags);
return buflen;
}
/****************************************************************************
* Name: pressure_poll
****************************************************************************/
static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct pressure_file_s *priv = filep->f_priv;
clock_t current = clock_systime_ticks();
uint32_t flags;
flags = spin_lock_irqsave(&g_pressure_lock);
if (setup)
{
if (priv->fds == NULL)
{
priv->fds = fds;
fds->priv = &priv->fds;
/* If the remaining memory is less than the threshold and
* lasttick is CLOCK_MAX, it means the event is triggered for
* the first time and we should always send a notification.
*/
if (g_remaining <= priv->threshold && (priv->lasttick ==
CLOCK_MAX || current - priv->lasttick >= priv->interval))
{
priv->lasttick = current;
spin_unlock_irqrestore(&g_pressure_lock, flags);
poll_notify(&priv->fds, 1, POLLPRI);
return OK;
}
}
else
{
spin_unlock_irqrestore(&g_pressure_lock, flags);
return -EBUSY;
}
}
else if (fds->priv)
{
priv->fds = NULL;
fds->priv = NULL;
}
spin_unlock_irqrestore(&g_pressure_lock, flags);
return OK;
}
/****************************************************************************
* Name: pressure_dup
****************************************************************************/
static int pressure_dup(FAR const struct file *oldp, FAR struct file *newp)
{
FAR struct pressure_file_s *oldpriv = oldp->f_priv;
FAR struct pressure_file_s *newpriv;
uint32_t flags;
newpriv = kmm_zalloc(sizeof(struct pressure_file_s));
if (newpriv == NULL)
{
return -ENOMEM;
}
flags = spin_lock_irqsave(&g_pressure_lock);
memcpy(newpriv, oldpriv, sizeof(struct pressure_file_s));
dq_addfirst(&newpriv->entry, &g_pressure_memory_queue);
newpriv->fds = NULL;
newp->f_priv = newpriv;
spin_unlock_irqrestore(&g_pressure_lock, flags);
return OK;
}
/****************************************************************************
* Name: pressure_opendir
****************************************************************************/
static int pressure_opendir(FAR const char *relpath,
FAR struct fs_dirent_s **dir)
{
FAR struct procfs_dir_priv_s *level;
finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
DEBUGASSERT(relpath);
level = kmm_zalloc(sizeof(struct procfs_dir_priv_s));
if (level == NULL)
{
return -ENOMEM;
}
level->level = 1;
level->nentries = 1;
*dir = (FAR struct fs_dirent_s *)level;
return OK;
}
/****************************************************************************
* Name: pressure_closedir
****************************************************************************/
static int pressure_closedir(FAR struct fs_dirent_s *dir)
{
DEBUGASSERT(dir);
kmm_free(dir);
return OK;
}
/****************************************************************************
* Name: pressure_readdir
****************************************************************************/
static int pressure_readdir(FAR struct fs_dirent_s *dir,
FAR struct dirent *entry)
{
FAR struct procfs_dir_priv_s *level;
DEBUGASSERT(dir);
level = (FAR struct procfs_dir_priv_s *)dir;
if (level->index >= level->nentries)
{
finfo("No more entries\n");
return -ENOENT;
}
entry->d_type = DTYPE_FILE;
strncpy(entry->d_name, "memory", sizeof(entry->d_name));
level->index++;
return OK;
}
/****************************************************************************
* Name: pressure_rewinddir
****************************************************************************/
static int pressure_rewinddir(FAR struct fs_dirent_s *dir)
{
FAR struct procfs_dir_priv_s *level;
DEBUGASSERT(dir);
level = (FAR struct procfs_dir_priv_s *)dir;
level->index = 0;
return OK;
}
/****************************************************************************
* Name: pressure_stat
****************************************************************************/
static int pressure_stat(const char *relpath, struct stat *buf)
{
memset(buf, 0, sizeof(struct stat));
if (strcmp(relpath, "pressure") == 0 || strcmp(relpath, "pressure/") == 0)
{
buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
}
else if (strcmp(relpath, "pressure/memory") == 0)
{
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR | S_IWOTH |
S_IWGRP | S_IWUSR;
}
else
{
ferr("ERROR: No such file or directory: %s\n", relpath);
return -ENOENT;
}
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* mm_notify_pressure
****************************************************************************/
void mm_notify_pressure(size_t remaining)
{
clock_t current = clock_systime_ticks();
FAR dq_entry_t *entry;
FAR dq_entry_t *tmp;
uint32_t flags;
flags = spin_lock_irqsave(&g_pressure_lock);
g_remaining = remaining;
dq_for_every_safe(&g_pressure_memory_queue, entry, tmp)
{
FAR struct pressure_file_s *pressure =
container_of(entry, struct pressure_file_s, entry);
if (remaining > pressure->threshold)
{
continue;
}
/* If lasttick is CLOCK_MAX, it means that the event is triggered
* for the first time and we should always send notifications.
*/
if (pressure->lasttick != CLOCK_MAX && current - pressure->lasttick <
pressure->interval)
{
continue;
}
/* If fds is NULL, it means no one is listening for the event and
* we should delay sending the notification.
*/
if (pressure->fds == NULL)
{
continue;
}
pressure->lasttick = current;
spin_unlock_irqrestore(&g_pressure_lock, flags);
poll_notify(&pressure->fds, 1, POLLPRI);
flags = spin_lock_irqsave(&g_pressure_lock);
}
spin_unlock_irqrestore(&g_pressure_lock, flags);
}

View File

@ -432,6 +432,14 @@ FAR void kmm_checkcorruption(void);
#endif /* CONFIG_DEBUG_MM */
/* Functions contained in fs_procfspressure.c *******************************/
#ifdef CONFIG_FS_PROCFS_INCLUDE_PRESSURE
void mm_notify_pressure(size_t remaining);
#else
# define mm_notify_pressure(remaining)
#endif
#undef EXTERN
#ifdef __cplusplus
}