nuttx/fs/procfs/fs_procfspressure.c
yinshengkai 49d1b4198f mm: add memory pressure notification support
Add mm_heap_free interface to pass remaining memory to memory pressure

Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
2024-08-25 23:09:28 +08:00

460 lines
14 KiB
C

/****************************************************************************
* 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;
size_t remain;
off_t offset;
ssize_t ret;
flags = spin_lock_irqsave(&g_pressure_lock);
remain = g_remaining;
spin_unlock_irqrestore(&g_pressure_lock, flags);
ret = procfs_snprintf(buf, sizeof(buf), "remaining %zu\n", remain);
if (ret > buflen)
{
return -ENOMEM;
}
offset = filep->f_pos;
ret = procfs_memcpy(buf, ret, buffer, buflen, &offset);
filep->f_pos += 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);
}