Demonios e Hilos

Demonios e Hilos

proceso de demonio

El proceso Daemon (elf) es un proceso de servicio en segundo plano en Linux , que suele ser independiente del terminal de control y periódicamente realiza ciertas tareas o espera para procesar ciertos eventos que ocurren . Generalmente use el nombre que termina con d, como vsftpd

Algunos procesos de servicio del sistema en el fondo de Linux no tienen terminal de control y no pueden interactuar directamente con los usuarios. No se ven afectados por el inicio y cierre de sesión del usuario, siempre se están ejecutando, todos son procesos daemon. Tales como: la realización del mecanismo de salida de prelectura y almacenamiento en búfer; servidor ftp; servidor nfs, etc.

Grupos de procesos y sesiones

ps ajxVer el ID del grupo de procesos y el ID de la sesión

grupo de proceso

Un grupo de procesos es una colección de uno o más procesos, y cada proceso pertenece a un grupo de procesos.El grupo de procesos se introduce para simplificar la gestión de procesos. Cuando el proceso principal crea un proceso secundario, el proceso secundario predeterminado y el proceso principal pertenecen al mismo grupo de procesos

identificación del grupo de procesoEl primer ID de proceso (proceso líder). Si el proceso principal crea varios procesos secundarios, el proceso principal y varios procesos secundarios pertenecen al mismo grupo, y dado que el proceso principal es el primer proceso en el grupo de procesos, el proceso principal es el líder del grupo y el grupo identificación de líderidentificación del proceso padre

Puede usar kill -SIGKILL - ID de grupo de procesos (negativo) para matar todos los procesos en todo el grupo de procesos

Siempre que haya un proceso en el grupo de procesos, el grupo de procesos existe, independientemente de si el proceso líder del grupo finaliza.

Tiempo de vida del grupo de procesos: desde la creación del grupo de procesos hasta la última salida del proceso

conversación

  • Una sesión es una colección de uno o más grupos de procesos.
  • El proceso que crea la sesión no puede ser el líder del grupo de procesos
  • El proceso que crea la sesión se convierte en el proceso líder de un grupo de procesos y también se convierte en el líder de la sesión.
  • Necesidad de tener privilegios de root (no se requiere ubuntu)
  • La sesión recién creada descarta el terminal de control original
  • Al establecer una nueva sesión, llame primero a la bifurcación, el proceso principal finaliza y el proceso secundario llama a la función setsid

imagen-20211124222937063

Crear un modelo de demonio

  1. bifurcación del proceso secundario, el proceso principal sale

El proceso secundario hereda el ID de grupo de procesos del proceso principal, pero tiene un nuevo ID de proceso, lo que garantiza que el proceso secundario no sea el ID líder de un grupo de procesos, lo cual es un requisito previo necesario para que la llamada de la función setsid sea hecho a continuación

  1. El proceso hijo llama a la función setsid para crear una nueva sesión

Después de llamar a esta función

  • Este proceso se convierte en el primer proceso de la nueva sesión y es el presidente de la sesión.
  • Conviértete en el proceso líder de un nuevo grupo de procesos, es el líder del grupo de procesos
  • No afectado por el terminal de control
  1. Cambiar el directorio de trabajo actual chdir

Por ejemplo: a.out está en el disco U, inicie este programa, el directorio de trabajo actual de este programa es este disco U, si el disco U está desconectado, el directorio de trabajo actual del proceso desaparecerá y a.out desaparecerá. no funciona normalmente.

  1. Restablecer el modo de máscara de archivo y ~mask
  • El proceso hijo heredará la máscara del proceso padre.
  • Aumente la flexibilidad de la operación del programa de subprocesos
  • umask(0000);
  1. cerrar descriptor de archivo
  • El daemon no se ve afectado por el terminal de control y se puede cerrar para liberar recursos
  • cerrar(STDIN_FILENO);
  • cerrar(STDOUT_FILENO);
  • cerrar(STDERR_FILENO);
  1. Realizar el trabajo central: la lógica del código central del proceso daemon

ejemplo

Escriba un proceso daemon para obtener la hora del sistema cada 2S y escriba esta hora en el archivo del disco

//创建守护进程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/time.h>
#include<signal.h>
#include<time.h>
#include<fcntl.h>
#include<sys/stat.h>

void myfunc(int signo)
{
    
    
    //打开文件
    int fd=open("mydemon.log",O_RDWR|O_CREAT,0755);
    if(fd<0)
    {
    
    
        return;
    }
    //获取当前系统时间
    time_t t;
    time(&t);
    char *p=ctime(&t);

    //将时间写入文件
    write(fd,p,strlen(p));
    close(fd);
    return;
}

int main()
{
    
    
    //父进程fork子进程,父进程退出
    pid_t pid=fork();
    if(pid<0||pid>0)
    {
    
    
        exit(1);
    }

    //子进程调用setsid函数创建会话
    setsid();

    //改变当前工作目录
    chdir("/usr/local/src/test01");

    //改变文件掩码
    umask(0000);

    //关闭标准输入、输出、错误输出文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    //核心操作
    
    //注册信号处理函数
    struct sigaction act;
    act.sa_handler=myfunc;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM,&act,NULL);

    //设置时钟
    struct itimerval tm;
    tm.it_interval.tv_sec=2;
    tm.it_interval.tv_usec=0;
    tm.it_value.tv_sec=3;
    tm.it_value.tv_usec=0;
    setitimer(ITIMER_REAL,&tm,NULL);

    while(1)
    {
    
    
        sleep(1);
    }
}

hilo

que es hilo

Proceso ligero (LWP: proceso ligero), la esencia de un subproceso en un entorno Linux sigue siendo un proceso

Proceso: tiene un espacio de direcciones independiente, tiene un PCB, lo que equivale a vivir solo

Subproceso: hay una PCB, pero no hay espacio de direcciones independiente, y varios subprocesos comparten el espacio de proceso, lo que equivale al alquiler compartido

imagen-20211125133010494

En el sistema operativo Linux, un subproceso es la unidad de ejecución más pequeña y un proceso es la unidad de asignación de recursos más pequeña.

ps -Lf pidVer el número LWP del hilo especificado

imagen-20211125133201619

Ya sea creando una bifurcación de proceso o creando un subproceso pthread_create, la implementación subyacente llama al mismo clon de función del núcleo

Todas las funciones de operación de subprocesos pthread_* son funciones de biblioteca, no llamadas al sistema

El kernel de Linux no distingue entre procesos e hilos, solo a nivel de usuario

subproceso de recursos compartidos

  • tabla de descriptores de archivos
  • Cómo se maneja cada señal
  • directorio de trabajo actual
  • ID de usuario e ID de grupo
  • Espacio de direcciones de memoria (.text/.data/.bss/heap/shared library)

Los hilos no comparten recursos.

  • identificador de subproceso
  • Subprocesos del procesador y punteros de pila (pila del núcleo)
  • Espacio de pila independiente (pila de espacio de usuario)
  • variable de error
  • palabra de máscara de señal
  • prioridad de programación

ventaja

  • Mejore la concurrencia del programa
  • bajo costo
  • La comunicación de datos y el intercambio de datos son convenientes

defecto

  • función de biblioteca, inestable
  • depuración de gdb, dificultades de escritura
  • Afortunadamente, el soporte no es bueno.

pthread_create

//创建一个新线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);

//成功,返回0 失败,返回错误号

//pthread_t:传出参数,保存系统为我们分配好的线程ID
//attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
//start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
//arg:线程主函数执行期间所使用的参数。

//由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印

//如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行
//创建子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    printf("child thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

imagen-20211125135610004

Bucle para crear múltiples subprocesos

//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    int i=*(int*)arg;
    printf("[%d]:child thread,pid==[%d],id==[%ld]\n",i,getpid(),pthread_self());
}

int main()
{
    
    
    int n=5;
    int i=0;
    //创建子线程
    pthread_t thread[5];
    for(i=0;i<5;i++)
    {
    
    
        int ret=pthread_create(&thread[i],NULL,mythread,&i);
        if(ret!=0)
        {
    
    
            printf("pthread_create error,[%s]\n",strerror(ret));
            return -1;
        }
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

imagen-20211125140218745

yo soy los 5

Al crear un subproceso, utilice el factor de bucle como parámetro para pasar al subproceso, de modo que el subproceso principal y varios subprocesos compartan la variable i (la variable i se define en la función principal y siempre está válido durante todo el proceso), por lo que en la vista del subproceso La variable i es espacio de memoria de pila legal

Entonces, ¿por qué el valor está impreso por cada subproceso al final 5?

Esto se debe a que el subproceso principal puede crear continuamente 5 subprocesos dentro de un segmento de tiempo de CPU. En este momento, el valor de la variable i se convierte en 5. Cuando el subproceso principal pierde el segmento de tiempo de CPU, el subproceso obtiene el tiempo de CPU. segmento, y el subproceso de acceso es el valor del espacio de memoria de la variable i, por lo que el valor impreso es 5

imagen-20211125140408189

Solución

Múltiples subprocesos no pueden compartir el mismo espacio de memoria. Cada subproceso debe acceder a diferentes espacios de memoria. Puede definir una matriz en el subproceso principal: int arr[5];, y luego pasar diferentes elementos de matriz al crear subprocesos. En este manera, cada subproceso secundario accede a un espacio de memoria diferente, de modo que se puede imprimir el valor correcto

imagen-20211125140505329

Si el subproceso principal sale antes que el subproceso secundario, es posible que el subproceso secundario no se ejecute, porque el subproceso principal sale, todo el espacio del proceso se reciclará y el subproceso secundario no tiene espacio vital, por lo que no se ejecutará.

Los subprocesos (incluidos el subproceso principal y los subprocesos) pueden compartir la misma variable, incluidas las variables globales o variables no globales (pero las variables no globales deben estar dentro de su vigencia efectiva)

//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
    int i=*(int*)arg;
    printf("[%d]:child thread,pid==[%d],id==[%ld]\n",i,getpid(),pthread_self());
}

int main()
{
    
    
    int n=5;
    int i=0;
    int a[5];
    //创建子线程
    pthread_t thread[5];
    for(i=0;i<5;i++)
    {
    
    
        a[i]=i+1;
        int ret=pthread_create(&thread[i],NULL,mythread,&a[i]);
        if(ret!=0)
        {
    
    
            printf("pthread_create error,[%s]\n",strerror(ret));
            return -1;
        }
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    sleep(1);
    return 0;
}

imagen-20211125140638259

pthread_exit

Está prohibido llamar a la función de salida en el subproceso, de lo contrario, se cerrará todo el proceso. En su lugar, llame a la función pthread_exit. Esta función es para hacer que un subproceso salga. Si el subproceso principal llama a la función pthread_exit, todo el proceso no se cerrará. y no afectará la ejecución de otros hilos.

//将单个线程退出

void pthread_exit(void *retval);	

//retval表示线程退出状态,通常传NULL

//pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收

pthread_join

//阻塞等待线程退出,获取线程退出状态。其作用,对应进程中的waitpid() 函数

int pthread_join(pthread_t thread, void **retval); 

//成功:0;失败:错误号

//thread:线程ID
//retval:存储线程结束状态,整个指针和pthread_exit的参数是同一块内存地址
//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

int g_var=9;

void* mythread(void *arg)
{
    
    
    printf("child thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
    printf("[%p]\n",&g_var);
    pthread_exit(&g_var);
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
   
    //回收子线程
    void* p=NULL;
    pthread_join(thread,&p);
    int a=*(int*)p;
    printf("child exit status:[%d],[%p]\n",a,p);
    return 0;
}

imagen-20211125142919651

pthread_detach

Estado de separación de subprocesos: especifique este estado y el subproceso se desconectará activamente del subproceso de control principal. Una vez que finaliza el subproceso, su estado de salida no es adquirido por otros subprocesos, sino que se libera automáticamente directamente por sí mismo. Red, servidor de subprocesos múltiples de uso común

Si el proceso tiene este mecanismo, no se generará ningún proceso zombie. La generación de procesos zombis se debe principalmente al hecho de que después de que el proceso muere, la mayoría de los recursos se liberan y algunos recursos residuales aún existen en el sistema, lo que hace que el núcleo piense que el proceso aún existe.

Puede usar el parámetro 2 de la función pthread_create (atributos de hilo) para establecer la separación de hilos

La función pthread_detach se llama después de que se crea el hilo

//实现线程分离

int pthread_detach(pthread_t thread);	

//成功:0;失败:错误号

//一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了
//循环创建子线程,并且打印是第几个子线程
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>

void* mythread(void *arg)
{
    
    
   while(1)
   {
    
    
       int a;
       int b;

       //设置取消点
       pthread_testcancel();
   }
}

int main()
{
    
    
    //创建子线程
    pthread_t thread;
    int ret=pthread_create(&thread,NULL,mythread,NULL);
    if(ret!=0)
    {
    
    
        printf("pthread_create error,[%s]\n",strerror(ret));
        return -1;
    }
    printf("main thread,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
   
    //取消子线程
    pthread_cancel(thread);

    pthread_join(thread,NULL);
    return 0;
}

pthread_cancel

//杀死(取消)线程。其作用,对应进程中 kill() 函数

int pthread_cancel(pthread_t thread);	

//成功:0;失败:错误号

//线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)
//取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。可粗略认为一个系统调用(进入内核)即为一个取消点。还以通过调用pthread_testcancel函数设置一个取消点

void pthread_testcancel(void);

pthread_igual

//比较两个线程ID是否相等

int pthread_equal(pthread_t t1, pthread_t t2);

propiedades del hilo

El estado de separación de un subproceso determina cómo se termina un subproceso Hay dos estados:

  • Estado no separado: el atributo predeterminado de un hilo es un estado no separado. En este caso, el hilo original espera a que finalice el hilo creado. Solo cuando la función pthread_join() regresa, el subproceso creado finaliza y los recursos del sistema ocupados por sí mismo pueden liberarse
  • Estado separado: el subproceso separado no es esperado por otros subprocesos. Después de ejecutarse solo, el subproceso finaliza y los recursos del sistema se liberan inmediatamente. Debe elegir el estado de separación adecuado según sus propias necesidades.
//定义线程属性类型类型的变量
pthread_attr_t  attr;	

//对线程属性变量进行初始化
int pthread_attr_init (pthread_attr_t* attr);

//设置线程为分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
//attr: 线程属性
//detachstate:
//PTHREAD_CREATE_DETACHED(分离)
//PTHREAD_CREATE_JOINABLE(非分离)
//这一步完成之后调用pthread_create函数创建线程,则创建出来的线程就是分离线程;其实上述三步就是pthread_create的第二个参数做准备工作

//释放线程属性资源
int pthread_attr_destroy(pthread_attr_t *attr);
//参数:线程属性

sincronización de subprocesos

La sincronización de subprocesos significa que cuando un subproceso emite una llamada de función, la llamada no regresa hasta que se obtiene el resultado. Al mismo tiempo, otros subprocesos no pueden llamar a esta función para garantizar la coherencia de los datos.

ejemplo

Cree dos subprocesos, deje que los dos subprocesos compartan un número int de variable global, y luego deje que cada subproceso cuente 5000 veces, y vea cuánto se imprime el valor del número al final

Después de muchas pruebas, los resultados finales muestran que puede haber casos en los que el valor numérico sea inferior a 5000*2=10000

Motivo: si el subproceso A termina de ejecutar la operación cur++ y no ha asignado el valor de cur al número, pierde el derecho de ejecución de la CPU, y el subproceso B obtiene el derecho de ejecución de la CPU, y el subproceso B finalmente ejecuta number= cur, y luego pierde el derecho a ejecutar la CPU; en este momento, el subproceso secundario A recupera el derecho a ejecutar la CPU y ejecuta la operación number=cur, que sobrescribirá el valor que el subproceso B acaba de escribir en number, haciendo que el valor de número no cumpla con el valor esperado

Razones para la confusión de datos

  • El intercambio de recursos
  • La programación es aleatoria (el orden en que los subprocesos operan en los recursos compartidos es incierto)
  • Falta del mecanismo de sincronización necesario entre subprocesos

Cómo resolver

  • Operación atómica: la operación no se realiza o se completa
  • Uso de Mutexes: simulación de operaciones atómicas

exclusión mutua

Linux proporciona un mutex mutex (también conocido como mutex). Cada subproceso intenta bloquear el recurso antes de operar en él. La operación solo se puede realizar después de un bloqueo exitoso, y la operación se completa y desbloquea.

Los recursos todavía se comparten y todavía hay competencia entre subprocesos, pero el acceso a los recursos se convierte en una operación mutuamente excluyente a través de "bloqueos", y luego ya no se producirán errores relacionados con el tiempo.

Cuando el subproceso 1 accede al recurso compartido, primero debe determinar si el bloqueo está bloqueado. Si está bloqueado, se bloqueará y esperará; otros subprocesos tienen la oportunidad de adquirir el bloqueo.

Al mismo tiempo, solo un subproceso puede mantener el bloqueo, siempre que el subproceso no haya completado la operación, el bloqueo no se liberará

Después de usar el mutex, los dos subprocesos cambiaron de operación paralela a operación en serie y se redujo la eficiencia, pero se resolvió el problema de la inconsistencia de datos.

función principal

//pthread_mutex_t 类型
//本质是一个结构体,为简化理解,应用时可简单当成整数看待
pthread_mutex_t mutex; 
//变量mutex只有两种取值1、0


//pthread_mutex_init函数
//初始化一个互斥锁(互斥量) ---> 初值可看作1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//mutex:传出参数,调用时应传 &mutex	
//attr:互斥锁属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)
//restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改互斥量mutex的两种初始化方式:
//静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
//动态初始化:局部变量应采用动态初始化
pthread_mutex_init(&mutex, NULL);


//pthread_mutex_destroy函数
//销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//mutex—互斥锁变量


//pthread_mutex_lock函数
//对互斥所加锁,可理解为将mutex--
int pthread_mutex_lock(pthread_mutex_t *mutex);
//mutex—互斥锁变量


//pthread_mutex_unlock函数
//对互斥所解锁,可理解为将mutex ++
int pthread_mutex_unlock(pthread_mutex_t *mutex);


//pthread_mutex_trylock函数
//尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//mutex—互斥锁变量

trabar y destrabar

El bloqueo intenta bloquearse. Si el bloqueo no tiene éxito, el subproceso se bloqueará hasta que se desbloqueen otros subprocesos que contienen la exclusión mutua
. Activación, según la prioridad, la programación. Predeterminado: bloquear primero, despertar primero

Supongo que te gusta

Origin blog.csdn.net/blll0/article/details/121537807
Recomendado
Clasificación