[Conceptos básicos de Linux] Linux embebido ---- uevento mecanismo: análisis del principio de uevento (artículo detallado)

Introducción:

Este artículo presenta principalmente qué es el mecanismo de uevent y el proceso de usar el mecanismo de uevent para generar nodos de dispositivo a través del análisis de código. Y este artículo se dividirá en dos partes, la primera parte presentamos algunos conocimientos preparatorios y el principio de uevent, y la segunda parte a través de la introducción del código para usar el mecanismo de uevent para crear un nodo de dispositivo.

Kernel de Linux: linux-2.6.22.6

Placa de desarrollo utilizada: JZ2440 V3 (S2C2440A)

declaración:

Este artículo está escrito principalmente después de ver el video del profesor Wei Dongshan y combinado con contenido de blog, por lo que puede haber contenido en otros artículos del artículo. Si cree que mi artículo constituye una infracción para usted, puede informarme y continuaré. con el artículo. Corrígelo y, por favor, corrígeme si hay algo incorrecto en el texto. Gracias.

Parte 1: Conocimientos preliminares y principio de uevent

A continuación comenzamos a explicar la preparación recién. Antes de explicar, echemos un vistazo a un diagrama de marco que presenta el mecanismo de uevent:

                  La imagen proviene de: modelo de dispositivo Linux (3) _Uevent

Es posible que muchos de nosotros no lo tengamos claro: ¿Cuál es el mecanismo de uevent y qué hace exactamente el mecanismo de uevent? ¿Qué aspectos merecen nuestro estudio?

Cuando aprendimos el controlador por primera vez, no usamos el mecanismo uevent, lo que significa que no usamos las funciones class_create y class_device_create en el programa para crear automáticamente el nodo de dispositivo para el controlador de dispositivo en el espacio de usuario. En ese momento, tuvimos que usar manualmente el comando mknod en el espacio de usuario para crear nodos de dispositivo. Y cuando usamos las funciones class_create y class_device_create, crearán un nodo de dispositivo en el espacio de usuario para nosotros en lugar de hacer este trabajo manualmente. Y esta es nuestra comprensión macro del mecanismo de eventos. Y sabemos que nuestros nodos de dispositivo se crean para un controlador de dispositivo, y el controlador de dispositivo y el controlador de dispositivo están conectados al bus de bus en forma de lista, mientras que el dispositivo - controlador - el bus al siguiente nivel es la capa sysfs . Entonces esto nos lleva a una combinación: sysfs + mdev . Y este par de combinaciones nos explicará el principio del mecanismo de uevent. Primero entendamos sysfs.

1.1 、 sysfs

sysfs es un sistema de archivos virtual basado en memoria, proporcionado por el kernel, montado en el directorio / sys (verifique con mount para obtener sysfs en / sys tipo sysfs (rw, nosuid, nodev, noexec, relatime )), responsable del dispositivo árbol El formulario proporciona información intuitiva del controlador y del dispositivo al espacio de nombres del usuario . Al mismo tiempo, sysfs nos muestra los dispositivos actualmente conectados al sistema desde una perspectiva diferente:

  • / sys / block que queda del historial, almacena dispositivos de bloqueo y proporciona un enlace simbólico a / sys / devices con el nombre del dispositivo (como sda);
  • / sys / bus se clasifica por tipo de bus. Puede encontrar enlaces simbólicos de dispositivos conectados al bus en un directorio de bus, apuntando a / sys / dispositivos. El directorio de controladores bajo cierto directorio de bus contiene los enlaces simbólicos de todos los controladores requeridos por el bus correspondientes a la estructura bus_type en el kernel;
  • / sys / class se clasifica según la función del dispositivo, por ejemplo, el dispositivo de entrada está debajo de / sys / class / input, y el dispositivo gráfico está debajo de / sys / class / graphics, que es el enlace simbólico que apunta al dispositivo correspondiente en el directorio / sys / devices en la clase Struct del kernel correspondiente;
  • / sys / dev   está estratificado de acuerdo con el controlador de dispositivo (dispositivo de caracteres / dispositivo de bloque), proporcionando un enlace simbólico llamado major: minor a / sys / devices correspondiente a la estructura device_driver en el kernel;
  • / sys / devices contiene todos los dispositivos físicos detectados registrados en varios buses. Todos los dispositivos físicos se muestran según su topología en el bus, excepto los dispositivos de plataforma y los dispositivos del sistema. Los dispositivos de plataforma generalmente se refieren a varios controladores y periféricos en el chip o en el bus de baja velocidad, que pueden ser direccionados directamente por la CPU. Los dispositivos del sistema no son periféricos. Son las estructuras centrales dentro de la ventosa, como CPU, temporizador, etc. Generalmente no tienen controladores relacionados, pero habrá algunos códigos relacionados con la arquitectura para configurarlos para que correspondan con el dispositivo de estructura en el núcleo.

Lo anterior muestra los archivos correspondientes al bus, dispositivo, controlador y clase en el directorio sys , y su relación es :

  • dispositivo se utiliza para describir varios dispositivos, lo que guarda toda la información del dispositivo;
  • El controlador se utiliza para controlar el dispositivo, que guarda la lista vinculada de todos los dispositivos que pueden ser controlados por él;
  • El bus es el puente que conecta la CPU y el dispositivo, guarda todas las listas vinculadas de dispositivos montados en él y las listas vinculadas de controladores que controlan estos dispositivos;
  • La clase se utiliza para describir un tipo de dispositivo, que guarda todas las listas vinculadas a dispositivos de este tipo de dispositivo .

A continuación, presentamos la estructura del bus, los dispositivos, los controladores y las clases del siguiente nivel. La función de sysfs se basa en el modelo de dispositivo unificado de Linux, que tiene la siguiente estructura: kobject, kset, ktype. Al mismo tiempo, podemos ver en el diagrama de bloques anterior que uevent se implementa sobre la base de la estructura kobject.

kobject: el objeto más básico del modelo de dispositivo unificado.

struct kobject {
 	const char *name;  //name,该Kobject的名称,同时也是sysfs中的目录名称。
			    //由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。
			   //如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。
        struct list_head    entry; //entry,用于将Kobject加入到Kset中的list_head。 
        struct kobject      *parent; //parent,指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。
        struct kset     *kset; //kset,该kobject属于的Kset。可以为NULL。
				//如果存在,且没有指定parent,则会把Kset作为parent
				//(别忘了Kset是一个特殊的Kobject)。
        struct kobj_type    *ktype;  //ktype,该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。
        struct sysfs_dirent *sd;   //sd,该Kobject在sysfs中的表示。
 
        struct kref     kref;  //kref,"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。
        unsigned int state_initialized:1; //state_initialized,指示该Kobject是否已经初始化,
					  //以在Kobject的Init,Put,Add等操作时进行异常校验。
        unsigned int state_in_sysfs:1;   //state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。	
        unsigned int state_add_uevent_sent:1;  // state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD uevent,
						//如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,
						//以便让用户空间正确处理。
        unsigned int state_remove_uevent_sent:1;
        unsigned int uevent_suppress:1;  //uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。
    };

Nota : Uevent proporciona la implementación de la función de "notificación de espacio de usuario", a través de esta función, cuando hay una adición, eliminación, modificación y otras acciones de kobject en el kernel, se notificará al espacio de usuario.

Ktype: representa el conjunto de operaciones de atributos de Kobject (estrictamente hablando, es la estructura de datos que contiene Kobject) (debido a la versatilidad, varios Kobjects pueden compartir el mismo conjunto de operaciones de atributos, por lo que Ktype es independiente).

struct kobj_type {
     void (*release)(struct kobject *kobj); //release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。
     const struct sysfs_ops *sysfs_ops;  //sysfs_ops,该种类型的Kobject的sysfs文件系统接口。
     struct attribute **default_attrs; //default_attrs,该种类型的Kobject的atrribute列表
					//(所谓attribute,就是sysfs文件系统中的一个文件)。
					//将会在Kobject添加到内核时,一并注册到sysfs中。
     const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); 
					//child_ns_type/namespace,和文件系统(sysfs)的命名空间有关
     const void *(*namespace)(struct kobject *kobj);
 };

De hecho, la implementación aquí es similar a la derivación de Kobject, y los kobjects que contienen diferentes tipos de kobj se pueden considerar como diferentes subclases. El polimorfismo se logra implementando la misma función. Bajo tal diseño, cada estructura de datos de kobject incrustado (como kset, dispositivo, deivce_driver, etc.) debe implementar su propio kobj_type y definir su función de devolución de llamada.

Kset: un Kobject especial (por lo que también aparecerá en forma de directorio en el sistema de archivos "/ sys /"), que se utiliza para agregar Kobjects similares (estos Kobjects pueden tener los mismos atributos o atributos diferentes) .

struct kset {
     struct list_head list; //list用于保存该kset下所有的kobject的链表。
     spinlock_t list_lock;  //list自旋锁
     struct kobject kobj;  //kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。
     const struct kset_uevent_ops *uevent_ops; //uevent_ops,该kset的uevent操作函数集。
						//当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,
						//或者过滤event(kset可以决定哪些event可以上报)。
						//因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
 };

La función de devolución de llamada de operación uevent de kset es:

struct kset_uevent_ops {
     int (* const filter)(struct kset *kset, struct kobject *kobj);
		//filter,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,
		//阻止不希望上报的event,从而达到从整体上管理的目的。
     const char *(* const name)(struct kset *kset, struct kobject *kobj);
		//name,该接口可以返回kset的名称。如果一个kset没有合法的名称,
		//则其下的所有Kobject将不允许上报uvent
     int (* const uevent)(struct kset *kset, struct kobject *kobj,
         struct kobj_uevent_env *env);
	//uevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
	//因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
 };

Preste atención a la asociación entre Kset y Ktype . Kobject usará el miembro kset para encontrar el kset al que pertenece, y luego establecerá su ktype en kobj.ktype. Cuando no se especifica ningún miembro de kset, se usa ktype para establecer la relación.

Dado que kobject llama a la función de operación uevent de su propio kset, kset puede controlar su comportamiento. Si kobject no pertenece a ningún kset, no se puede enviar uevent.

En resumen, la comprensión de Ktype y todo el mecanismo de Kobject:

La función principal de Kobject es mantener un recuento de referencias.Cuando el recuento se reduce a 0, el espacio de memoria ocupado por Kobject se libera automáticamente (mediante el módulo kobject descrito en este artículo). Esto determina que Kobject debe asignarse dinámicamente (solo de esta manera puede liberarse dinámicamente) .

La mayoría de los escenarios de uso de Kobject están integrados en grandes estructuras de datos (como kset, device_driver, etc.), por lo que estas grandes estructuras de datos también deben asignarse y liberarse dinámicamente . Entonces, ¿cuál es el momento del lanzamiento? Es cuando se lanza el Kobject incrustado. Pero el lanzamiento de Kobject se realiza automáticamente mediante el módulo Kobject (cuando el recuento de referencias es 0), entonces , ¿cómo liberar la gran estructura de datos que se contiene a sí mismo?

Aquí es donde Ktype resulta útil. Sabemos que la función de devolución de llamada de liberación de Ktype es responsable de liberar el espacio de memoria de Kobject (incluso la estructura de datos que contiene Kobject) Entonces, ¿quién implementa Ktype y sus funciones internas? ¡Es el módulo donde se ubica la estructura de datos superior! Porque solo está claro en qué estructura de datos está incrustado el Kobject y, a través del puntero de Kobject y su propio tipo de estructura de datos, busque el puntero de la estructura de datos superior que debe liberarse y luego suéltelo .

Hablando de esto, es mucho más claro. Por lo tanto, cada estructura de datos con Kobject incrustado, como kset, device, device_driver, etc., debe implementar un Ktype y definir su función de devolución de llamada. De la misma manera, las operaciones relacionadas con sysfs deben pasar por la transferencia de ktype, porque sysfs ve Kobject, y el cuerpo principal de la operación de archivo real es la estructura de datos superior del Kobject incrustado.

Por cierto, Kobject es la máxima manifestación del pensamiento orientado a objetos en el Kernel de Linux, pero las ventajas del lenguaje C no están aquí, por lo que el Kernel de Linux debe implementarse con medios más inteligentes (y muy prolijos).

1.2, principio mdev

Analizamos sysfs anteriormente, y luego comenzamos a analizar mdev. Entendemos su relación con sysfs analizando mdev. mdev se puede encontrar en el paquete de código busybox, ubicado en el archivo busybox / util-linux / mdev.c , y es llamado por la función uevent_helper. Dos cosas se hacen principalmente en mdev:

la primera cosa:

Cuando se ejecuta el comando mdev -s, mdev escanea / sys / block (los dispositivos de bloque se almacenan en el directorio / sys / block, y después de la versión 2.6.25 del kernel, los dispositivos de bloque también se almacenan en el directorio / sys / class / block .mdev escanea / sys / block es para compatibilidad con versiones anteriores) y el archivo de atributos dev en el directorio / sys / class. El número de dispositivo se obtiene del archivo de atributos dev (el archivo de atributos dev guarda el número de dispositivo en forma de "mayor : minor \ n "), y use el nombre del directorio que contiene el archivo de atributos dev como nombre del dispositivo nombre_dispositivo (es decir, el directorio que contiene el archivo de atributos dev se llama nombre_dispositivo y la parte del directorio entre / sys / class y nombre_dispositivo se llama subsistema, es decir, cada atributo dev La ruta donde se encuentra el archivo se puede expresar como / sys / class / subsystem / nombre_dispositivo / dev), y el archivo de dispositivo correspondiente se crea en el directorio / dev . Por ejemplo, cat / sys / class / tty / tty0 / dev obtendrá 4: 0, el subsistema es tty y el nombre_dispositivo es tty0.

La segunda cosa:

Cuando se llama a mdev debido al evento uevent (anteriormente llamado evento hotplug), mdev obtiene a través de las variables de entorno que le pasa el evento uevent: la acción del dispositivo que causó el evento uevent y la ruta del dispositivo del dispositivo. Luego, determine qué acción causó el evento uevento. Y haga la operación correspondiente según la acción diferente . Si se agrega la acción, se agrega un nuevo dispositivo al sistema. Independientemente de si el dispositivo es un dispositivo virtual o un dispositivo físico real, mdev obtendrá el número de dispositivo a través del archivo de atributo dev debajo de la ruta del dispositivo y luego usará el El último directorio de la ruta del dispositivo (el directorio que contiene el archivo de atributos dev) se utiliza como nombre del dispositivo y el archivo del dispositivo correspondiente se crea en el directorio / dev. Si la acción es eliminar, es decir, el dispositivo se eliminó del sistema, elimine el archivo del dispositivo con el último nombre de directorio de la ruta del dispositivo como nombre de archivo en el directorio / dev. Si la acción no es agregar ni eliminar, mdev no hace nada.

De lo anterior, si queremos crear y eliminar automáticamente archivos de dispositivo por mdev cuando el dispositivo se agrega o se elimina del sistema, entonces debemos hacer tres cosas:

  1. En cierto directorio del subsistema de / sys / class;
  2. Cree un directorio con el nombre del dispositivo device_name como nombre;
  3. Y se debe incluir un archivo de atributos dev en el directorio nombre_dispositivo. El archivo de atributos dev genera el número de dispositivo en forma de "mayor: menor \ n".

Por el contenido anterior, podemos saber que sysfs hace el trabajo preparatorio para el mecanismo uevent, es decir, crea el directorio correspondiente, mientras que mdev implementa la creación de nodos de dispositivo llamando a los directorios o archivos creados por sysfs sobre la base de sysfs.

Parte 2: Combine la introducción de código y use el mecanismo de uevent para crear nodos de dispositivo

Ahora estamos a punto de analizar el mecanismo de uevent en combinación con el código, y para analizar este mecanismo, necesitamos analizar cómo se implementa este proceso a partir de las dos funciones de class_create y class_device_create. Analicemos class_create primero:

class_create(THIS_MODULE,"buttonsdrv");
    class_register(cls);
        kobject_set_name(&cls->subsys.kobj, "%s", cls->name); //将类的名字led_class赋值给对应的kset
	subsys_set_kset(cls, class_subsys);
	subsystem_register(&cls->subsys);
	    kset_register(s);  //创建class设备类目录
		kset_add(k);
	            kobject_add(&k->kobj);
			kobject_shadow_add(kobj, NULL);
		            parent = kobject_get(kobj->parent); // parent即class_kset.kobj,即/sysfs/class对应的目录
			    list_add_tail(&kobj->entry,&kobj->kset->list);
			    create_dir(kobj, shadow_parent);  //创建一个class设备类目录
				sysfs_create_dir(kobj, shadow_parent); //该接口是sysfs文件系统接口,代表创建一个目录,不再展开。

De lo anterior podemos ver que kobject corresponde a un directorio (dir) en sysfs.Cuando registramos un kobject, llamaremos kobject_add (& k-> kobj); y luego crearemos un directorio de dispositivos de clase. Al mismo tiempo, podemos ver que la función class_create hace el cuasi-trabajo del catálogo para la función class_device_create.

Por la imagen introducida al principio del artículo, sabemos que cuando cambia el estado de kobject (como: agregar, eliminar, etc.), se notificará al espacio de usuario, y el espacio de usuario puede hacer el procesamiento correspondiente después de recibir la notificación del evento.

Hay dos formas de que uevent informe el tiempo al espacio de usuario:

  1. A través del módulo kmod, llame directamente a programas ejecutables o scripts en el espacio del usuario;
  2. Pase el evento del espacio del kernel al espacio del usuario a través del mecanismo de comunicación netlink;

Este artículo explica principalmente cómo llamar directamente programas o scripts ejecutables en el espacio del usuario a través del módulo kmod . A través de kobject.h, el módulo uevent proporciona las siguientes API (la implementación de estas API se encuentra en el archivo "lib / kobject_uevent.c"):

int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
                         char *envp[]);
  
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
   
int kobject_action_type(const char *buf, size_t count,enum kobject_action *type);

Comencemos con el análisis de la función class_device_create para ver cómo llegó a la función kobject_uevent. Observamos la relación jerárquica de la función class_device_create:

class_device_create(buttonsdrv_class,NULL,MKDEV(auto_major,0),NULL,"buttonsdrv");
	class_device_register(class_dev);
		class_device_add(class_dev);
			class_dev = class_device_get(class_dev);
			parent_class = class_get(class_dev->class);
			parent_class_dev = class_device_get(class_dev->parent);
			kobject_set_name(&class_dev->kobj, "%s", class_dev->class_id);
			kobject_add(&class_dev->kobj);
			class_device_create_file(class_dev, attr);
			class_device_add_groups(class_dev);
			make_deprecated_class_device_links(class_dev);
			kobject_uevent(&class_dev->kobj, KOBJ_ADD);

En el código anterior, podemos ver que class_create y class_device_create hacen mucho trabajo similar, es decir, para crear un directorio. Cuando se alcanza la función kobject_uevent, son diferentes. Analicemos la función kobjec_uevent.

/**
 * 通过终端事件通知用户层
 *
 * @action: 发生的事件 (通常是 KOBJ_ADD 和 KOBJ_REMOVE)
 * @kobj: 事件发生的kobject 结构体
 *
 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
	return kobject_uevent_env(kobj, action, NULL);
}

Llamó a la función kobject_uevent_env, y en la introducción anterior, sabíamos que el evento debería enviarse, entonces , ¿qué eventos hay?

Observamos linux-3.5 / include / linux / kobject.h :

 enum kobject_action {   
     KOBJ_ADD,  //ADD/REMOVE,Kobject(或上层数据结构)的添加/移除事件。
     KOBJ_REMOVE,    
     KOBJ_CHANGE, //CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变。
		  //CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,
		  //或者是自定义的事件,可以使用该event,并携带相应的参数。
     KOBJ_MOVE,  //MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。
     KOBJ_ONLINE, //ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
     KOBJ_OFFLINE,
     KOBJ_MAX 
 };

A continuación continuamos analizando la función kobject_uevent_env :

/**
 * 发送一个带有环境变量的事件
 *
 * @action: 发生的事件(通常为KOBJ_MOVE)
 * @kobj: 事件发生的kobject结构体
 * @envp_ext: 环境变量数据指针
 *
 */
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
			char *envp_ext[])
{
	action_string = action_to_string(action);
 
	/* 查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的) */
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent) {
		top_kobj = top_kobj->parent;
	}
	if (!top_kobj->kset) {
		pr_debug("kobject attempted to send uevent without kset!\n");
		return -EINVAL;
	}
 
	kset = top_kobj->kset;
	uevent_ops = kset->uevent_ops;
 
	/*  如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event) */
	if (uevent_ops && uevent_ops->filter)
		if (!uevent_ops->filter(kset, kobj)) {
			pr_debug("kobject filter function caused the event to drop!\n");
			return 0;
		}
 
	/*判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem;若没有合法的名称,则不上报uevent */
	if (uevent_ops && uevent_ops->name)
		subsystem = uevent_ops->name(kset, kobj);
	else
		subsystem = kobject_name(&kset->kobj);
	if (!subsystem) {
		pr_debug("unset subsytem caused the event to drop!\n");
		return 0;
	}
 
	/* 分配一个此次上报的环境变量 */
	envp = kzalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL);
	if (!envp)
		return -ENOMEM;
 
	/*分配一个此次上报的用于保存环境变量的buffer, */
	buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
	if (!buffer) {
		retval = -ENOMEM;
		goto exit;
	}
 
	/* 获得该kobject在sysfs中路径 */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}
 
	/* uevent_helper的环境变量*/
	envp[i++] = "HOME=/";
	envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
 
	/* 添加环境变量 */
	scratch = buffer;
	envp [i++] = scratch;
	scratch += sprintf(scratch, "ACTION=%s", action_string) + 1;
	envp [i++] = scratch;
	scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1;
	envp [i++] = scratch;
	scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1;
	for (j = 0; envp_ext && envp_ext[j]; j++)
		envp[i++] = envp_ext[j];
	/* just reserve the space, overwrite it after kset call has returned */
	envp[i++] = seq_buff = scratch;
	scratch += strlen("SEQNUM=18446744073709551616") + 1;
 
	/* 如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针 */
	if (uevent_ops && uevent_ops->uevent) {
		retval = uevent_ops->uevent(kset, kobj,
				  &envp[i], NUM_ENVP - i, scratch,
				  BUFFER_SIZE - (scratch - buffer));
		if (retval) {
			pr_debug ("%s - uevent() returned %d\n",
				  __FUNCTION__, retval);
			goto exit;
		}
	}
 
	/* 调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号 */
	spin_lock(&sequence_lock);
	seq = ++uevent_seqnum;
	spin_unlock(&sequence_lock);
	sprintf(seq_buff, "SEQNUM=%llu", (unsigned long long)seq);
 
 
	/* 以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent。 */
	if (uevent_helper[0]) {
		char *argv [3];
 
		argv [0] = uevent_helper;
		argv [1] = (char *)subsystem;
		argv [2] = NULL;
		call_usermodehelper (argv[0], argv, envp, 0);
	}
}

Cuando el módulo uevent informa uevent a través de kmod, llamará al archivo ejecutable del espacio de usuario (o script, ayudante uevent para abreviar) para procesar el evento a través de la función call_usermodehelper. La ruta del ayudante uevent se almacena en la matriz uevent_helper. Puede especificar estáticamente el ayudante de uevent a través del elemento de configuración CONFIG_UEVENT_HELPER_PATH al compilar el kernel.

Pero este método bifurcará un proceso para cada evento.A medida que aumenta el número de dispositivos admitidos por el kernel, este método será fatal cuando el sistema se inicie (puede causar desbordamiento de memoria, etc.) Por lo tanto, este método solo se usa en versiones anteriores del kernel y ya no es recomendado por el kernel. Por lo tanto, al compilar el kernel, debe dejar este elemento de configuración en blanco. Una vez que se inicia el sistema, la mayoría de los dispositivos están listos, puede reasignar un ayudante de eventos según sea necesario para detectar eventos de conexión en caliente durante el funcionamiento del sistema.

Esto se puede lograr escribiendo la ruta del ayudante en el archivo "/ sys / kernel / uevent_helper". De hecho, el kernel abre la matriz uevent_helper en el espacio del usuario en la forma del sistema de archivos sysfs para la modificación y el acceso al programa del espacio del usuario. Para obtener más detalles, consulte el código correspondiente en "./kernel/ksysfs.c".

Agregue echo "/ sbin / mdev"> / proc / sys / kernel / hotplug en el script /etc/init.d/rcS, encontrará que cat / sys / kernel / uevent_helper es / sbin / mdev. Significa que la ruta del archivo ejecutable en / proc / sys / kernel / hotplug eventualmente se escribirá en / sys / kernel / uevent_helper . Manualmente echo "/ kernel / main"> uevent_helper (se sobrescribirá el / sbin / mdev anterior), cuando se ejecuten lsmod, rmmod, / kernel / main en / sys / kernel / uevent_helper, lo que indica que se ha informado de la hora a el espacio de usuario.

Veamos cómo crear un nodo de dispositivo en Busybox.

Es el turno de mdev para salir. Todas las descripciones anteriores crean directorios o archivos en el sistema de archivos sysfs, y los archivos de dispositivo a los que accede la aplicación deben crearse en el directorio / dev /. Este trabajo lo realiza mdev.

El principio de mdev es explicar las reglas para nombrar archivos de dispositivo definidos en el archivo /etc/mdev.conf y crear archivos de dispositivo basados ​​en los requisitos de las variables de entorno bajo las reglas. mdev.conf está especificado por el nivel de usuario, por lo que es más flexible. Este artículo no pretende ampliar el análisis del script de configuración mdev. Para conocimientos relacionados, consulte mi traducción: traducción mdev.conf

El programa mdev correspondiente está en Busybox / util-linux / mdev.c:

int mdev_main(int argc UNUSED_PARAM, char **argv)
    xchdir("/dev");
    if (argv[1] && strcmp(argv[1], "-s")//系统启动时mdev –s才会执行这个分支
    else
    action = getenv("ACTION");
    env_path = getenv("DEVPATH");
    G.subsystem = getenv("SUBSYSTEM");
    snprintf(temp, PATH_MAX, "/sys%s", env_path);//到/sysfs/devices/led目录
    make_device(temp, /*delete:*/ 0);
    strcpy(dev_maj_min, "/dev"); //读出dev属性文件,得到设备号
    open_read_close(path, dev_maj_min + 1, 64);    
    ….
    mknod(node_name, rule->mode | type, makedev(major, minor)) //最终mknod创建节点

Eventualmente, rastrearemos que mknod creó un archivo de dispositivo en el directorio / dev.

referencias:

Las referencias explican en detalle, ¡vale la pena una visita! ! ! ----- ¡Gracias por el arduo trabajo del autor original! ! !

 

Supongo que te gusta

Origin blog.csdn.net/u014674293/article/details/115083363
Recomendado
Clasificación