[Noções básicas do Linux] Embedded Linux ---- mecanismo de evento: análise de princípio de evento (artigo detalhado)

Introdução:

Este artigo apresenta principalmente o que é o mecanismo uevent e o processo de uso do mecanismo uevent para gerar nós de dispositivo por meio de análise de código. E este artigo será dividido em duas partes, a primeira parte apresentamos alguns conhecimentos preparatórios e o princípio de uevent, e a segunda parte através da introdução do código para usar o mecanismo uevent para criar um nó de dispositivo.

Kernel do Linux: linux-2.6.22.6

Placa de desenvolvimento usada: JZ2440 V3 (S2C2440A)

demonstração:

Este artigo foi escrito principalmente após assistir ao vídeo do professor Wei Dongshan e combinado com algum conteúdo do blog, então pode haver conteúdo em outros artigos no artigo. Se você acha que meu artigo constitui uma violação sua, pode me dizer e eu irei prosseguir com o artigo. Corrija-o e corrija-me se houver algo incorreto no texto. Obrigada.

Parte 1: Conhecimento preliminar e o princípio de uevento

Abaixo começamos a explicar a preparação apenas. Antes de explicar, vamos dar uma olhada em um diagrama de estrutura que apresenta o mecanismo uevent:

                  A imagem vem de: modelo de dispositivo Linux (3) _Uevent

Muitos de nós podem não ser claros: o que é o mecanismo uevent e o que exatamente faz o mecanismo uevent? Quais aspectos dela são dignos de nosso estudo?

Quando aprendemos o driver pela primeira vez, não usamos o mecanismo uevent, o que significa que não usamos as funções class_create e class_device_create no programa para criar automaticamente o nó de dispositivo para o driver de dispositivo no espaço do usuário. Naquela época, tínhamos que usar manualmente o comando mknod no espaço do usuário para criar nós de dispositivo. E quando usamos as funções class_create e class_device_create, elas criarão um nó de dispositivo no espaço do usuário para nós, em vez de fazer esse trabalho manualmente. E esta é a nossa compreensão macro do mecanismo de evento. E sabemos que nossos nós de dispositivo são criados para um driver de dispositivo, e o driver de dispositivo e o driver de dispositivo são conectados ao barramento de barramento na forma de lista, enquanto o driver de dispositivo - o barramento para o próximo nível é a camada sysfs . Portanto, isso nos leva a uma combinação: sysfs + mdev . E este par de combinações explicará o princípio do mecanismo de evento para nós. Vamos primeiro entender o sysfs.

1.1 、 sysfs

sysfs é um sistema de arquivos virtual baseado em memória, fornecido pelo kernel, montado no diretório / sys (verifique com mount para obter sysfs em / sys digite sysfs (rw, nosuid, nodev, noexec, relatime )), responsável pelo dispositivo árvore O formulário fornece informações intuitivas sobre o dispositivo e o driver para o namespace do usuário . Ao mesmo tempo, sysfs nos mostra os dispositivos atualmente conectados ao sistema de uma perspectiva diferente:

  • / sys / block que sobrou do histórico, armazenar dispositivos de bloco e fornecer um link simbólico para / sys / devices com o nome do dispositivo (como sda);
  • / sys / bus é classificado por tipo de barramento. Você pode encontrar links simbólicos de dispositivos conectados ao barramento em um diretório de barramento, apontando para / sys / devices. O diretório de drivers em um determinado diretório de barramento contém os links simbólicos de todos os drivers exigidos pelo barramento correspondente ao struct bus_type no kernel;
  • / sys / class é classificado pela função do dispositivo, por exemplo, o dispositivo de entrada está em / sys / class / input, e o dispositivo gráfico está em / sys / class / graphics, que é o link simbólico apontando para o dispositivo correspondente no diretório / sys / devices na classe Struct do kernel correspondente;
  • / sys / dev   é dividido em camadas de acordo com o driver de dispositivo (dispositivo de caractere / dispositivo de bloco), fornecendo um link simbólico denominado major: minor para / sys / devices correspondente à struct device_driver no kernel;
  • / sys / devices contém todos os dispositivos físicos descobertos registrados em vários barramentos. Todos os dispositivos físicos são exibidos de acordo com sua topologia no barramento, exceto dispositivos de plataforma e dispositivos de sistema. Dispositivos de plataforma geralmente se referem a vários controladores e periféricos no chip ou no barramento de baixa velocidade, que podem ser endereçados diretamente pela CPU. Os dispositivos do sistema não são periféricos. Eles são as estruturas centrais dentro do sugador, como CPU, temporizador, etc. Eles geralmente não têm drivers relacionados, mas haverá alguns códigos relacionados à arquitetura para configurá-los para corresponder ao dispositivo de estrutura em o kernel.

O acima mostra os arquivos correspondentes ao barramento, dispositivo, driver e classe no diretório sys , e sua relação é :

  • dispositivo é usado para descrever vários dispositivos, o que salva todas as informações do dispositivo;
  • O driver é usado para acionar o dispositivo, o que salva a lista vinculada de todos os dispositivos que podem ser acionados por ele;
  • O barramento é a ponte que conecta a CPU e o dispositivo. Ele salva todas as listas vinculadas de dispositivos montados nele e as listas vinculadas de drivers que acionam esses dispositivos;
  • classe é usada para descrever um tipo de dispositivo, que salva todas as listas vinculadas de dispositivos desse tipo de dispositivo .

Abaixo, apresentamos a estrutura do barramento, dispositivos, drivers e classes no próximo nível. A função do sysfs é baseada no modelo de dispositivo unificado do Linux, que possui a seguinte estrutura: kobject, kset, ktype. Ao mesmo tempo, podemos ver no diagrama de blocos acima que uevent é implementado com base na estrutura kobject.

kobject: O objeto mais básico no 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 fornece a implementação da função "notificação do espaço do usuário", através desta função, quando houver adição, exclusão, modificação e outras ações do kobject no kernel, o espaço do usuário será notificado.

Ktype: Representa o conjunto de operações de atributos de Kobject (estritamente falando, é a estrutura de dados que contém Kobject) (devido à versatilidade, vários Kobjects podem compartilhar o mesmo conjunto de operações de atributos, então Ktype é independente).

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);
 };

Na verdade, a implementação aqui é semelhante à derivação de Kobject, e kobjects contendo kobj_types diferentes podem ser considerados subclasses diferentes. O polimorfismo é obtido implementando a mesma função. Sob tal projeto, cada estrutura de dados do kobject embutido (como kset, device, deivce_driver, etc.) deve implementar seu próprio kobj_type e definir sua função de retorno de chamada.

Kset: Um Kobject especial (por isso também aparecerá na forma de um diretório no sistema de arquivos "/ sys /"), que é usado para agregar Kobjects semelhantes (esses Kobjects podem ter os mesmos atributos ou 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的。
 };

A função de retorno de chamada da operação uevent de kset é:

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 atenção à associação entre Kset e Ktype . O Kobject usará o membro kset para encontrar o kset ao qual ele pertence e, em seguida, definirá seu ktype como kobj.ktype. Quando nenhum membro kset é especificado, ktype é usado para estabelecer o relacionamento.

Como o kobject chama a função de operação uevent de seu próprio kset, o kset pode controlar seu comportamento. Se kobject não pertencer a nenhum kset, uevent não poderá ser enviado.

Em resumo, a compreensão do Ktype e de todo o mecanismo Kobject:

A função central do Kobject é manter uma contagem de referência.Quando a contagem é reduzida a 0, o espaço de memória ocupado pelo Kobject é liberado automaticamente (pelo módulo kobject descrito neste artigo). Isso determina que o Kobject deve ser alocado dinamicamente (somente dessa forma pode ser liberado dinamicamente) .

A maioria dos cenários de uso do Kobject são incorporados em grandes estruturas de dados (como kset, device_driver, etc.), portanto, essas grandes estruturas de dados também devem ser alocadas e liberadas dinamicamente . Então, qual é o momento do lançamento? É quando o Kobject incorporado é lançado. Mas a liberação do Kobject é feita automaticamente pelo módulo Kobject (quando a contagem de referência é 0), então como liberar a grande estrutura de dados que a contém?

É aqui que o Ktype é útil. Sabemos que a função de retorno de chamada de liberação do Ktype é responsável por liberar o espaço de memória do Kobject (mesmo a estrutura de dados que contém o Kobject). Então, quem implementa o Ktype e suas funções internas? É o módulo onde a estrutura de dados superior está localizada! Porque só está claro em qual estrutura de dados o Kobject está embutido, e por meio do ponteiro do Kobject e seu próprio tipo de estrutura de dados, encontre o ponteiro da estrutura de dados superior que precisa ser liberado e, em seguida, libere-o .

Falando nisso, é muito mais claro. Portanto, toda estrutura de dados com Kobject embutido, como kset, device, device_driver, etc., deve implementar um Ktype e definir sua função de retorno de chamada. Da mesma forma, as operações relacionadas a sysfs devem passar pela transferência de ktype, porque sysfs vê Kobject, e o corpo principal da operação de arquivo real é a estrutura de dados superior do Kobject embutido!

A propósito, Kobject é a manifestação final do pensamento orientado a objetos no Linux Kernel, mas as vantagens da linguagem C não estão aqui, então o Linux Kernel precisa ser implementado com meios mais inteligentes (e muito prolixos).

1.2, princípio mdev

Analisamos o sysfs acima e, em seguida, começamos a analisar o mdev. Entendemos sua relação com o sysfs analisando o mdev. mdev pode ser encontrado no pacote de código busybox, localizado no arquivo busybox / util-linux / mdev.c , e é chamado pela função uevent_helper. Duas coisas são feitas principalmente no mdev:

a primeira coisa:

Quando o comando mdev -s é executado, mdev verifica / sys / block (os dispositivos de bloco são armazenados no diretório / sys / block e, após a versão 2.6.25 do kernel, os dispositivos de bloco também são armazenados no diretório / sys / class / block .mdev scans / sys / block é para compatibilidade com versões anteriores) e o arquivo de atributo dev no diretório / sys / class. O número do dispositivo é obtido do arquivo de atributo dev (o arquivo de atributo dev salva o número do dispositivo na forma de "principal : minor \ n "), E use o nome do diretório que contém o arquivo de atributo dev como o nome do dispositivo device_name (ou seja, o diretório que contém o arquivo de atributo dev é chamado device_name e a parte do diretório entre / sys / class e device_name é chamado subsystem. Ou seja, cada atributo dev O caminho onde o arquivo está localizado pode ser expresso como / sys / class / subsystem / device_name / dev), e o arquivo de dispositivo correspondente é criado no diretório / dev . Por exemplo, cat / sys / class / tty / tty0 / dev obterá 4: 0, o subsistema é tty e o device_name é tty0.

A segunda coisa:

Quando mdev é chamado devido ao evento uevent (anteriormente chamado de evento hotplug), mdev obtém por meio das variáveis ​​de ambiente passadas a ele pelo evento uevent: a ação do dispositivo que causou o evento uevent e o caminho do dispositivo do dispositivo. Em seguida, determine qual ação causou o evento uevent. E faça a operação correspondente de acordo com a ação diferente . Se a ação for adicionar, um novo dispositivo será adicionado ao sistema. Independentemente de o dispositivo ser um dispositivo virtual ou físico real, o mdev obterá o número do dispositivo por meio do arquivo de atributo dev no caminho do dispositivo e, em seguida, usará o último diretório do caminho do dispositivo. (O diretório que contém o arquivo de atributo dev) é usado como o nome do dispositivo e o arquivo do dispositivo correspondente é criado no diretório / dev. Se a ação for remover, ou seja, o dispositivo foi removido do sistema, exclua o arquivo do dispositivo com o último nome do diretório do caminho do dispositivo como o nome do arquivo no diretório / dev. Se a ação não for adicionar nem remover, o mdev não fará nada.

Do exposto acima, se quisermos criar e excluir arquivos de dispositivo automaticamente por mdev quando o dispositivo for adicionado ou removido do sistema, devemos fazer três coisas:

  1. Em um determinado diretório de subsistema de / sys / class;
  2. Crie um diretório com o nome do dispositivo nome_do_dispositivo como o nome;
  3. E um arquivo de atributo dev deve ser incluído no diretório device_name. O arquivo de atributo dev mostra o número do dispositivo na forma de "principal: secundário \ n".

Pelo conteúdo acima, podemos saber que sysfs faz o trabalho preparatório para o mecanismo uevent, ou seja, cria o diretório correspondente, enquanto mdev implementa a criação de nós de dispositivo chamando os diretórios ou arquivos criados por sysfs com base em sysfs.

Parte 2: Combine a introdução do código e use o mecanismo uevent para criar nós de dispositivo

Agora estamos prestes a analisar o mecanismo uevent em combinação com o código e, para analisar esse mecanismo, precisamos analisar como esse processo é implementado a partir das duas funções de class_create e class_device_create. Vamos analisar class_create primeiro:

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文件系统接口,代表创建一个目录,不再展开。

Do acima, podemos ver que kobject corresponde a um diretório (dir) no sysfs. Quando registramos um kobject, chamaremos kobject_add (& k-> kobj) e, em seguida, criaremos um diretório de dispositivo de classe. Ao mesmo tempo, podemos ver que a função class_create faz o quase trabalho do catálogo para a função class_device_create.

Pela imagem apresentada no início do artigo, sabemos que quando o estado do kobject muda (como: adicionar, remover, etc.), o espaço do usuário será notificado, e o espaço do usuário pode fazer o processamento correspondente após o recebimento a notificação do evento.

Existem duas maneiras de o uevent relatar o tempo para o espaço do usuário:

  1. Através do módulo kmod, chame diretamente programas executáveis ​​ou scripts no espaço do usuário;
  2. Passe o evento do espaço do kernel para o espaço do usuário por meio do mecanismo de comunicação netlink;

Este artigo explica principalmente como chamar diretamente programas executáveis ​​ou scripts no espaço do usuário por meio do módulo kmod . Por meio de kobject.h, o módulo uevent fornece as seguintes APIs (a implementação dessas APIs está no arquivo "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);

Vamos começar com a análise da função class_device_create para ver como ele chegou à função kobject_uevent. Vemos a relação hierárquica da função 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);

No código anterior, podemos ver que class_create e class_device_create fazem muito trabalho semelhante, ou seja, para criar um diretório. Quando a função kobject_uevent é alcançada, eles são diferentes. Vamos analisar a função 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);
}

Ele chamou a função kobject_uevent_env e, na introdução acima, sabíamos que o evento deveria ser enviado, então quais eventos existem?

Vemos 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 seguir, continuamos a analisar a função 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);
	}
}

Quando o módulo uevent relata uevent por meio de kmod, ele chama o arquivo executável do espaço do usuário (ou script, auxiliar uevent para abreviar) para processar o evento por meio da função call_usermodehelper. O caminho do auxiliar uevent é armazenado na matriz uevent_helper. Você pode especificar estaticamente o auxiliar uevent através do item de configuração CONFIG_UEVENT_HELPER_PATH ao compilar o kernel.

Mas esse método bifurca um processo para cada uevent. Conforme o número de dispositivos suportados pelo kernel aumenta, esse método será fatal quando o sistema iniciar (pode causar estouro de memória, etc.). Portanto, esse método só é usado em versões anteriores do kernel e não é mais recomendado pelo kernel. Portanto, ao compilar o kernel, você precisa deixar este item de configuração em branco. Depois que o sistema é iniciado, a maioria dos dispositivos está pronta, você pode reatribuir um ajudante uevent conforme necessário para detectar eventos hot plug durante a operação do sistema.

Isso pode ser feito escrevendo o caminho do helper no arquivo "/ sys / kernel / uevent_helper". Na verdade, o kernel abre o array uevent_helper para o espaço do usuário na forma do sistema de arquivos sysfs para modificação e acesso do programa do espaço do usuário. Para obter detalhes, consulte o código correspondente em "./kernel/ksysfs.c".

Adicione echo "/ sbin / mdev"> / proc / sys / kernel / hotplug no script /etc/init.d/rcS, você descobrirá que cat / sys / kernel / uevent_helper é / sbin / mdev. Isso significa que o caminho do arquivo executável em / proc / sys / kernel / hotplug será eventualmente gravado em / sys / kernel / uevent_helper . Faça echo manual de "/ kernel / main"> uevent_helper (o / sbin / mdev anterior será sobrescrito), quando lsmod, rmmod, / kernel / main em / sys / kernel / uevent_helper for executado, indicando que o tempo foi relatado para o espaço do usuário.

Vamos ver como criar um nó de dispositivo no Busybox.

É a vez de o mdev sair. Todas as descrições anteriores criam diretórios ou arquivos no sistema de arquivos sysfs, e os arquivos de dispositivo acessados ​​pelo aplicativo precisam ser criados no diretório / dev /. Este trabalho é feito por mdev.

O princípio do mdev é explicar as regras para nomear arquivos de dispositivo definidos no arquivo /etc/mdev.conf e criar arquivos de dispositivo com base nos requisitos das variáveis ​​de ambiente de acordo com as regras. mdev.conf é especificado pelo nível do usuário, portanto, é mais flexível. Este artigo não pretende expandir a análise do script de configuração mdev. Para conhecimentos relacionados, por favor, veja minha tradução: tradução mdev.conf

O programa mdev correspondente está em 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 se o mknod criou um arquivo de dispositivo no diretório / dev.

referências:

As referências explicam em detalhes, vale a pena uma visita! ! ! ----- Obrigado pelo trabalho árduo do autor original! ! !

 

Acho que você gosta

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