c++ - tecnicas - Método de archivo de bloqueo óptimo
programa para ocultar carpetas en android (6)
Windows tiene una opción para abrir un archivo con derechos de acceso exclusivos. Unix no lo hace.
Para garantizar el acceso exclusivo a algún archivo o dispositivo, es una práctica común en Unix usar un archivo de bloqueo normalmente almacenado en el directorio / var / lock.
La instrucción C open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 )
devuelve -1 si el archivo de bloqueo ya existe, de lo contrario, lo crea. La función es atómica y asegura que no hay condición de raza.
Cuando se libera el recurso, el archivo de bloqueo se elimina con la siguiente instrucción remove( "/var/lock/myLock.lock" )
.
Hay dos problemas con este método.
El programa puede terminar sin quitar el bloqueo. Por ejemplo, porque se mata, se estrella o lo que sea. El archivo de bloqueo permanece en su lugar e impedirá el acceso al recurso aunque ya no se use.
El archivo de bloqueo se crea con privilegios de escritura de grupo y mundo, pero es una práctica común configurar cuentas para usar una máscara de permiso que borre el permiso de escritura de grupo y mundo. Por lo tanto, si tuviéramos un método confiable para determinar que el bloqueo es huérfano (no de uso), un usuario que no sea el propietario del archivo no podrá eliminarlo.
Para el registro, uso el archivo de bloqueo para garantizar el acceso exclusivo al dispositivo conectado al puerto serie (/ dev / ttyUSBx de hecho). El método consultivo, que requiere cooperación, está bien. Pero el acceso exclusivo debe ser asegurado entre diferentes usuarios.
¿Hay un mejor método de sincronización que el archivo de bloqueo? ¿Cómo determinar si el proceso que creó el archivo de bloqueo todavía se está ejecutando? ¿Cómo hacer posible que otro usuario elimine el archivo de bloqueo si no está en uso?
Una solución que se me ocurrió fue usar el archivo como archivo de socket de Unix. Si el archivo existe, intente conectarse utilizando el archivo. Si falla, podemos asumir que el proceso del propietario del archivo está muerto. Esto requiere tener un subproceso en el socket accept()
en el proceso del propietario. Desafortunadamente, el sistema ya no sería atómico.
Eche un vistazo a la presentación esclarecedora Trucos y trampas de bloqueo de archivos :
Esta breve charla presenta varios inconvenientes comunes del bloqueo de archivos y algunos trucos útiles para usar el bloqueo de archivos de manera más efectiva.
Editar: Para responder a sus preguntas con más precisión:
¿Hay un mejor método de sincronización que el archivo de bloqueo?
Como ya se mencionó @Hasturkun y como se indicó en la presentación anterior, la llamada al sistema que necesita usar es flock() . Si el recurso que desea compartir entre muchos usuarios ya está basado en archivos (en su caso, es /dev/ttyUSBx
), entonces puede /dev/ttyUSBx
el archivo del dispositivo .
¿Cómo determinar si el proceso que creó el archivo de bloqueo todavía se está ejecutando?
No tiene que determinar esto, ya que el bloqueo flocado se liberará automáticamente al cerrar el descriptor de archivo asociado con su archivo, incluso si el proceso se terminó.
¿Cómo es posible que otro usuario elimine el archivo de bloqueo si no está en uso?
Si desea bloquear el archivo del dispositivo, no será necesario eliminar el archivo. Incluso si decide bloquear un archivo ordinario en /var/lock
, con flock
no necesitará eliminar el archivo para liberar el bloqueo.
Estaba usando el código publicado por chmike y noté una pequeña imperfección. Tuve un problema con la carrera durante la apertura del archivo de bloqueo. A veces, varios hilos abren el archivo de bloqueo simultáneamente.
Por lo tanto, quité el "remove (lockName);" línea desde la función "releaseLock ()". No entiendo por qué, pero de alguna manera esta acción ayudó a la situación.
He estado usando el siguiente código para probar los archivos de bloqueo. Por su salida se puede ver cuando varios hilos comienzan a usar un bloqueo simultáneamente.
void testlock(void) {
# pragma omp parallel num_threads(160)
{
int fd = -1; char ln[] = "testlock.lock";
while (fd == -1) fd = tryGetLock(ln);
cout << omp_get_thread_num() << ": got the lock!";
cout << omp_get_thread_num() << ": removing the lock";
releaseLock(fd,ln);
}
}
La respuesta de Hasturkun es la que me ha puesto en marcha.
Aquí está el código que uso
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
/*! Try to get lock. Return its file descriptor or -1 if failed.
*
* @param lockName Name of file used as lock (i.e. ''/var/lock/myLock'').
* @return File descriptor of lock file, or -1 if failed.
*/
int tryGetLock( char const *lockName )
{
mode_t m = umask( 0 );
int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
umask( m );
if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
{
close( fd );
fd = -1;
}
return fd;
}
/*! Release the lock obtained with tryGetLock( lockName ).
*
* @param fd File descriptor of lock returned by tryGetLock( lockName ).
* @param lockName Name of file used as lock (i.e. ''/var/lock/myLock'').
*/
void releaseLock( int fd, char const *lockName )
{
if( fd < 0 )
return;
remove( lockName );
close( fd );
}
Para ampliar la respuesta de Hasturhun. En lugar de utilizar la presencia o ausencia del archivo de bloqueo como indicador, debe crear el archivo de bloqueo si no existe y obtener un bloqueo exclusivo en el archivo.
Las ventajas de este enfoque es que, a diferencia de muchos otros métodos de sincronización de programas, el sistema operativo debería ordenar por usted si su programa sale sin desbloquear.
Entonces la estructura del programa sería algo así como:
1: open the lock file creating it if it doesn''t exist 2: ask for an exclusive lock an agreed byte range in the lock file 3: when the lock is granted then 4: <do my processing here> 5: release my lock 6: close the lock file end
En el paso: puede bloquear esperando que se otorgue el bloqueo o devolverlo inmediatamente. Los bytes que bloquea no tienen que existir en el archivo. Si puede obtener una copia de Advanced Unix Programming de Marc J. Rochkind, él desarrolla una biblioteca de C completa que utiliza este método para proporcionar una manera de sincronizar los programas que el sistema operativo pone en orden, sin embargo, los programas terminan.
Probablemente debería usar flock()
, como en
fd = open(filename, O_RDWR | O_CREAT, 0666); // open or create lockfile
//check open success...
rc = flock(fd, LOCK_EX | LOCK_NB); // grab exclusive lock, fail if can''t obtain.
if (rc)
{
// fail
}
Tenga cuidado con las funciones de bloqueo y liberación de bloqueo implementadas como se menciona en una de las respuestas, es decir, así:
1: open the lock file creating it if it doesn''t exist
2: ask for an exclusive lock an agreed byte range in the lock file
3: when the lock is granted then
4: <do my processing here>
5: release my lock
6: close the lock file
end
y:
void releaseLock( int fd, char const *lockName ) { if( fd < 0 ) return; remove( lockName ); close( fd ); }
El problema es que la llamada de eliminación de releaseLock introduce un error de situación de carrera. Consideremos que hay tres procesos que intentan adquirir el rebaño exclusivo con una sincronización desagradable:
- El proceso # 1 abrió el archivo de bloqueo, adquirió el rebaño y está a punto de llamar a la función de desbloqueo, pero aún no lo ha hecho.
- El proceso # 2 ha llamado a abrir para abrir el archivo señalado como LockName, y tiene un descriptor de archivo, pero aún no se llama flock. Es decir, el archivo apuntado por lockName se abre dos veces ahora.
- El proceso # 3 aún no ha comenzado.
Con una sincronización desagradable, es posible que el proceso # 1 llame primero a remove () y close () (el orden no importa), y luego el proceso # 2 llama a la bandada utilizando el descriptor de archivo ya abierto, pero que ya no es el archivo llamado lockName pero un descriptor de archivo que no está vinculado a ninguna entrada de directorio.
Ahora, si se inicia el proceso # 3, la llamada open () crea el archivo lockName y obtiene el bloqueo ya que ese archivo no está bloqueado. Como resultado, los procesos # 2 y # 3 piensan que ambos poseen el bloqueo en fileName -> un error.
El problema en la implementación es que remove () (o más unlink ()) solo desvincula el nombre de la entrada del directorio; el descriptor de archivo que se refiere a ese archivo todavía es utilizable. Uno puede crear otro archivo con el mismo nombre, pero aún el fd ya abierto se refiere a un lugar diferente.
Esto se puede demostrar agregando retardo a la función de bloqueo:
int tryGetLock( char const *lockName) { mode_t m = umask( 0 ); int fd = open( lockName, O_RDWR|O_CREAT, 0666 ); umask( m ); printf("Opened the file. Press enter to continue..."); fgetc(stdin); printf("Continuing by acquiring the lock./n"); if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 ) { close( fd ); fd = -1; } return fd; } static const char *lockfile = "/tmp/mylock.lock"; int main(int argc, char *argv[0]) { int lock = tryGetLock(lockfile); if (lock == -1) { printf("Getting lock failed/n"); return 1; } printf("Acquired the lock. Press enter to release the lock..."); fgetc(stdin); printf("Releasing..."); releaseLock(lock, lockfile); printf("Done!/n"); return 0;
}
- Intente iniciar el proceso # 1, y presione Enter una vez para adquirir el bloqueo.
- Luego comienza el proceso # 2 en otra terminal,
- Presione otra entrada en el terminal donde se está ejecutando el proceso # 1 para liberar el bloqueo. 4. Continúe con el proceso # 2 presionando enter una vez para que adquiera el bloqueo.
- Luego abre otra terminal donde ejecutar el proceso # 3. Allí, presiona enter una vez para adquirir el bloqueo.
Lo "imposible" sucede: los procesos # 2 y # 3 piensan que ambos tienen el bloqueo exclusivo.
Esto puede ser raro que ocurra en la práctica al menos con las aplicaciones habituales, pero sin embargo, la implementación no es correcta.
Además, crear un archivo con el modo 0666 puede ser un riesgo de seguridad.
No tengo "reputación para comentar", y esta es también una pregunta bastante antigua, pero la gente todavía se está refiriendo a esto y está haciendo algo similar, por eso es por eso que debo agregar esta nota como respuesta.