problema - windows 10 no se apaga se reinicia
Cómo evitar que se vacíe al disco de un mapa de memoria abierto en un archivo temporal de eliminación de cierre de Windows (2)
Después de que el período de recompensa expiró sin ninguna respuesta que proporcionara más información o resolviera el problema mencionado, decidí profundizar un poco más y experimentar un poco más con varias combinaciones y secuencias de operaciones.
Como resultado, creo que he encontrado una forma de lograr mapas de memoria compartidos entre procesos sobre archivos temporales de eliminación de cierre que no se descargan al disco cuando están cerrados.
La idea básica consiste en crear el mapa de memoria cuando se crea un archivo temporal con un nombre de mapa que se puede usar en una llamada a OpenFileMapping
:
// build a unique map name from the file name.
auto map_name = make_map_name(file_name);
// Open or create the mapped file.
auto mh = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, map_name.c_str());
if (mh == 0 || mh == INVALID_HANDLE_VALUE) {
// existing map could not be opened, create the file.
auto fh = ::CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, CREATE_NEW, kTempFlags, 0);
if (fh != INVALID_HANDLE_VALUE) {
// set its size.
LARGE_INTEGER newpos;
newpos.QuadPart = desired_size;
::SetFilePointerEx(fh, newpos, 0, FILE_BEGIN);
::SetEndOfFile(fh);
// create the map
mh = ::CreateFileMappingA(mh, nullptr, PAGE_READWRITE, 0, 0, map_name.c_str());
// close the file handle
// from now on there will be no accesses using file handles.
::CloseHandle(fh);
}
}
Por lo tanto, el identificador de archivo solo se usa cuando el archivo se crea nuevamente y se cierra inmediatamente después de que se crea el mapa, mientras que el identificador del mapa permanece abierto, para permitir la apertura de la asignación sin requerir acceso a un identificador de archivo. Tenga en cuenta que existe una condición de carrera aquí, que tendríamos que tratar en cualquier "código real" (además de agregar un control y una comprobación de errores decentes).
Entonces, si tenemos un identificador de mapa válido, podemos crear la vista :
auto map_ptr = MapViewOfFile(mh, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (map_ptr) {
// determine its size.
MEMORY_BASIC_INFORMATION mbi;
if (::VirtualQuery(map_ptr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)) > 0)
map_size = mbi.RegionSize;
}
Cuando, un tiempo después, cierre un archivo asignado: cierre el identificador del mapa antes de eliminar la marca de la vista:
if (mh == 0 || mh == INVALID_HANDLE_VALUE) {
::CloseHandle(mh);
mh = INVALID_HANDLE_VALUE;
}
if (map_ptr) {
::UnmapViewOfFile(map_ptr);
map_ptr = 0;
map_size = 0;
}
Y, de acuerdo con la prueba que he realizado hasta ahora, esto no causa que se vacíen las páginas sucias del disco al cerrar, problema resuelto . Bueno, en parte de todos modos, aún puede haber un problema de intercambio de nombres de mapas entre sesiones.
ACTUALIZACIÓN 2 / TL; DR
¿Hay alguna manera de evitar que se vacíen las páginas sucias de un archivo de eliminación de cierre temporal de Windows como resultado del cierre de los mapas de memoria abiertos en estos archivos?
Sí. Si no necesita hacer nada con los archivos después de su creación inicial e implementa algunas convenciones de nomenclatura, esto es posible a través de la estrategia explicada en esta respuesta .
Nota: Todavía estoy bastante interesado en descubrir las razones de por qué hay tanta diferencia en el comportamiento dependiendo de cómo se crean los mapas y el orden de eliminación / eliminación del mapeo.
He estado investigando algunas estrategias para una estructura de datos de memoria compartida entre procesos que permite aumentar y reducir su capacidad comprometida en Windows mediante el uso de una cadena de "fragmentos de memoria".
Una posible forma es usar mapas de memoria con nombre respaldados por archivos de paginación como la memoria de fragmentos. Una ventaja de esta estrategia es la posibilidad de usar SEC_RESERVE
para reservar una gran porción de espacio de direcciones de memoria y asignar incrementalmente usando VirtualAlloc
con MEM_COMMIT
. Las desventajas parecen ser (a) el requisito de tener permisos de SeCreateGlobalPrivilege
para permitir el uso de un nombre que se pueda compartir en el espacio de nombres Global/
y (b) el hecho de que toda la memoria comprometida contribuye a la carga de confirmación del sistema.
Para evitar estas desventajas, comencé a investigar el uso de mapas de memoria respaldados por archivos temporales . Es decir, mapas de memoria sobre archivos creados con el FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY
FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY
banderas FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY
. Esta parece ser una estrategia recomendada que de acuerdo con, por ejemplo, esta publicación de blog debe evitar el vaciado de la memoria mapeada en el disco (a menos que la presión de la memoria haga que las páginas mapeadas sucias se paginen).
Sin embargo, estoy observando que al cerrar el identificador de mapa / archivo antes de que finalice el proceso de propiedad, las páginas sucias se vacían en el disco. Esto ocurre incluso si el manejador de vista / archivo no es a través del cual se crearon las páginas sucias y cuándo se abrieron estas vistas / manejadores de archivo después de que las páginas se "ensuciaron" en una vista diferente.
Parece que cambiar el orden de eliminación (es decir, eliminar el mapeo primero de la vista o cerrar primero el identificador del archivo) tiene algún impacto sobre cuándo se inicia el vaciado del disco, pero no sobre el hecho de que se realiza el vaciado.
Entonces mis preguntas son:
ACTUALIZAR Alguna información adicional: al ejecutar las partes "arena1" y "arena2" del código de muestra a continuación en dos procesos independientes (independientes), siendo "arena1" el proceso que crea las regiones de memoria compartida y "arena2" el que abre ellos, el siguiente comportamiento se observa para los mapas / trozos que tienen páginas sucias:
- ¿Hay alguna forma de utilizar mapas temporales de memoria respaldados por archivos y evitar que vacíen las páginas sucias cuando se cierra el mapa / archivo, teniendo en cuenta que varios hilos dentro de un proceso / procesos múltiples pueden tener manejadores / vistas abiertas a dicho archivo?
- Si no, ¿cuál es / podría ser la razón del comportamiento observado?
- ¿Conoces una estrategia alternativa que pueda haber pasado por alto?
- Si cierra la vista antes del control del archivo en el proceso "arena1", vacía cada uno de estos fragmentos en el disco en lo que parece un proceso (parcialmente) síncrono (es decir, bloquea el hilo de eliminación durante varios segundos), independientemente de si el El proceso "arena2" se inició.
- Si cierra el identificador de archivo antes de la vista, los vaciados de disco solo se producen para los mapas / fragmentos que se cierran en el proceso "arena1" mientras que el proceso "arena2" todavía tiene un identificador abierto para esos fragmentos y parecen ser ''asíncronos'' , es decir, sin bloquear el hilo de la aplicación.
Consulte el código de ejemplo (c ++) a continuación que permite reproducir el problema en mi sistema (x64, Win7):
static uint64_t start_ts;
static uint64_t elapsed() {
return ::GetTickCount64() - start_ts;
}
class PageArena {
public:
typedef uint8_t* pointer;
PageArena(int id, const char* base_name, size_t page_sz, size_t chunk_sz, size_t n_chunks, bool dispose_handle_first) :
id_(id), base_name_(base_name), pg_sz_(page_sz), dispose_handle_first_(dispose_handle_first) {
for (size_t i = 0; i < n_chunks; i++)
chunks_.push_back(new Chunk(i, base_name_, chunk_sz, dispose_handle_first_));
}
~PageArena() {
for (auto i = 0; i < chunks_.size(); ++i) {
if (chunks_[i])
release_chunk(i);
}
std::cout << "[" << ::elapsed() << "] arena " << id_ << " destructed" << std::endl;
}
pointer alloc() {
auto ptr = chunks_.back()->alloc(pg_sz_);
if (!ptr) {
chunks_.push_back(new Chunk(chunks_.size(), base_name_, chunks_.back()->capacity(), dispose_handle_first_));
ptr = chunks_.back()->alloc(pg_sz_);
}
return ptr;
}
size_t num_chunks() {
return chunks_.size();
}
void release_chunk(size_t ndx) {
delete chunks_[ndx];
chunks_[ndx] = nullptr;
std::cout << "[" << ::elapsed() << "] chunk " << ndx << " released from arena " << id_ << std::endl;
}
private:
struct Chunk {
public:
Chunk(size_t ndx, const std::string& base_name, size_t size, bool dispose_handle_first) :
map_ptr_(nullptr), tail_(nullptr),
handle_(INVALID_HANDLE_VALUE), size_(0),
dispose_handle_first_(dispose_handle_first) {
name_ = name_for(base_name, ndx);
if ((handle_ = create_temp_file(name_, size)) == INVALID_HANDLE_VALUE)
handle_ = open_temp_file(name_, size);
if (handle_ != INVALID_HANDLE_VALUE) {
size_ = size;
auto map_handle = ::CreateFileMappingA(handle_, nullptr, PAGE_READWRITE, 0, 0, nullptr);
tail_ = map_ptr_ = (pointer)::MapViewOfFile(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
::CloseHandle(map_handle); // no longer needed.
}
}
~Chunk() {
if (dispose_handle_first_) {
close_file();
unmap_view();
} else {
unmap_view();
close_file();
}
}
size_t capacity() const {
return size_;
}
pointer alloc(size_t sz) {
pointer result = nullptr;
if (tail_ + sz <= map_ptr_ + size_) {
result = tail_;
tail_ += sz;
}
return result;
}
private:
static const DWORD kReadWrite = GENERIC_READ | GENERIC_WRITE;
static const DWORD kFileSharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
static const DWORD kTempFlags = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
static std::string name_for(const std::string& base_file_path, size_t ndx) {
std::stringstream ss;
ss << base_file_path << "." << ndx << ".chunk";
return ss.str();
}
static HANDLE create_temp_file(const std::string& name, size_t& size) {
auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, CREATE_NEW, kTempFlags, 0);
if (h != INVALID_HANDLE_VALUE) {
LARGE_INTEGER newpos;
newpos.QuadPart = size;
::SetFilePointerEx(h, newpos, 0, FILE_BEGIN);
::SetEndOfFile(h);
}
return h;
}
static HANDLE open_temp_file(const std::string& name, size_t& size) {
auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, OPEN_EXISTING, kTempFlags, 0);
if (h != INVALID_HANDLE_VALUE) {
LARGE_INTEGER sz;
::GetFileSizeEx(h, &sz);
size = sz.QuadPart;
}
return h;
}
void close_file() {
if (handle_ != INVALID_HANDLE_VALUE) {
std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closing" << std::endl;
::CloseHandle(handle_);
std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closed" << std::endl;
}
}
void unmap_view() {
if (map_ptr_) {
std::cout << "[" << ::elapsed() << "] " << name_ << " view closing" << std::endl;
::UnmapViewOfFile(map_ptr_);
std::cout << "[" << ::elapsed() << "] " << name_ << " view closed" << std::endl;
}
}
HANDLE handle_;
std::string name_;
pointer map_ptr_;
size_t size_;
pointer tail_;
bool dispose_handle_first_;
};
int id_;
size_t pg_sz_;
std::string base_name_;
std::vector<Chunk*> chunks_;
bool dispose_handle_first_;
};
static void TempFileMapping(bool dispose_handle_first) {
const size_t chunk_size = 256 * 1024 * 1024;
const size_t pg_size = 8192;
const size_t n_pages = 100 * 1000;
const char* base_path = "data/page_pool";
start_ts = ::GetTickCount64();
if (dispose_handle_first)
std::cout << "Mapping with 2 arenas and closing file handles before unmapping views." << std::endl;
else
std::cout << "Mapping with 2 arenas and unmapping views before closing file handles." << std::endl;
{
std::cout << "[" << ::elapsed() << "] " << "allocating " << n_pages << " pages through arena 1." << std::endl;
PageArena arena1(1, base_path, pg_size, chunk_size, 1, dispose_handle_first);
for (size_t i = 0; i < n_pages; i++) {
auto ptr = arena1.alloc();
memset(ptr, (i + 1) % 256, pg_size); // ensure pages are dirty.
}
std::cout << "[" << elapsed() << "] " << arena1.num_chunks() << " chunks created." << std::endl;
{
PageArena arena2(2, base_path, pg_size, chunk_size, arena1.num_chunks(), dispose_handle_first);
std::cout << "[" << ::elapsed() << "] arena 2 loaded, going to release chunks 1 and 2 from arena 1" << std::endl;
arena1.release_chunk(1);
arena1.release_chunk(2);
}
}
}
Consulte esta idea que contiene el resultado de ejecutar el código anterior y enlaces a capturas de pantalla de la memoria libre del sistema y la actividad del disco al ejecutar TempFileMapping(false)
y TempFileMapping(true)
respectivamente.
Si lo tomo correctamente, al comentar la parte del código Arena2
reproduciremos el problema sin necesidad de un segundo proceso. He intentado esto:
Edité
base_path
siguiente manera por conveniencia:char base_path[MAX_PATH]; GetTempPathA(MAX_PATH, base_path); strcat_s(base_path, MAX_PATH, "page_pool");
- Edité
n_pages = 1536 * 128
para llevar la memoria usada a 1.5GB, en comparación con tus ~ 800mb. - He probado
TempFileMapping(false)
yTempFileMapping(true)
, uno a la vez, para obtener los mismos resultados. - He probado con
Arena2
comentada e intacta, por los mismos resultados. - He probado en Win8.1 x64 y Win7 x64, para ± 10% mismos resultados.
- En mis pruebas, el código se ejecuta en 2400ms ± 10%, solo 500ms ± 10% se gasta en la desasignación. Claramente, eso no es suficiente para una descarga de 1.5GB en discos duros silenciosos y silenciosos que tengo allí.
Entonces, la pregunta es, ¿qué estás observando? Sugeriría que:
- Proporcione sus tiempos para la comparación
- Use una computadora diferente para las pruebas, preste atención a la exclusión de problemas de software como "el mismo antivirus"
- Verifica que no estás experimentando una escasez de RAM.
- Use xperf para ver lo que sucede durante el congelamiento.
Actualización He probado en otro Win7 x64, y los tiempos son 890ms completos, 430ms gastados en dealloc. He analizado sus resultados, y lo que es MUY sospechoso es que casi exactamente 4000ms se gastan en congelación cada vez en su máquina. Eso no puede ser una mera coincidencia, creo. Además, es bastante obvio ahora que el problema está ligado de algún modo a una máquina específica que estás usando. Entonces mis sugerencias son:
- Como se indicó anteriormente, pruebe usted mismo en otra computadora
- Como se mencionó anteriormente, use XPerf, le permitirá ver qué sucede exactamente en el modo de usuario y en el kernel durante la congelación (realmente sospecho que hay algún controlador no estándar en el medio)
- Juega con el número de páginas y observa cómo afecta la longitud de congelación.
- Intente almacenar archivos en una unidad de disco diferente en la misma computadora donde lo haya probado inicialmente.