Linux的UDEV机制


udev 机制引入:

 手机接入Linux热拔插相关
a. 把手机接入开发板
b. 安装adb工具,在终端输入adb安装指令: sudo apt-get install adb
c. dmeg能查看到手机接入的信息,但是输入adb devices会出现提醒
dinsufficient permissions for device: user in plugdev group; are your udev
rules wrong?
d. 配置文件,以支持USB设备的热拔插,支持UDEV的机制
在/etc/udev/rules.d 文件夹下创建规则文件
cd /etc/udev/rules.d/
sudo vim 51-android.rules
在文件中添加内容 SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666"
e. 在手机开发者选项中,打开USB调试,重新

udev 概念引入: 

udev是一个设备管理工具,udev以守护进程的形式运行,通过侦听内核发出来的uevent来管
/dev目录下的设备文件。udev在用户空间运行,而不在内核空间 运行。它能够根据系统中的硬
件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。
使用udev后,在/dev目录下就只包含系统中真正存在的设备


------------------------------------------------


图形理解: 

下面是一张linux 架构图: 

Linux 下面 一切都是文件  -- open 来打开

调用过程: 

我们在应用层 调用  open 函数(存放在库函数中)  -->  库函数中的open 系统调用 sys_open
 --> 系统调用的 sys_read  再调用内核的 kernel_open  -->  内核的kernal_open 负责调用硬件的寄存器处理 


-------------------------------------

查看进程基础与技巧: 

一般形式:

ps -elf | grep a.out  

#unix标准风格组合,其中-e 代表列出所有进程,-l 代表长格式,-f 代表完整的格式

消除grep干扰

在平时查看进程得到时候一般都会 多出一个 grep 进程---> 影响判断

比如下图 中的第二个进程  grep: 


我们可以这样

忽略 grep进程:   ps -elf | grep a.out | grep -v grep  

       //检索 a.out 的进程,同时忽略 grep 进程

& -- 指定进程后台运行


./a.out &    // 后台进程 a.out

//  普通程序 依托 于终端,终端关闭就结束

init -- 进程 pid  - 1 

显示行号

    显示行号:末行模式下输入 **set number** 或 **set nu** 回车
    关闭行号:末行模式下输入 **set nonumber** 或 **set nonu** 回车

守护进程:

概念

Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行
某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个
系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。

常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。守护进程的名称通常以d结尾

UDEV守护进程,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。


基本特点


1)生存周期长[非必须],一般操作系统启动的时候就启动,关闭的时候关闭。
2)守护进程和终端无关联,也就是他们没有控制终端,所以当控制终端退出,也不会导致守护进程退出
3)守护进程是在后台运行,不会占着终端,终端可以执行其他命令

4)一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程
linux操作系统本身是有很多的守护进程在默默执行,维持着系统的日常活动。大概30-50个

普通守护进程   和 内核适合进程

ppid = 0:内核进程,跟随系统启动而启动,生命周期贯穿整个系统。
cmd列名带[]这种,叫内核守护进程
老祖init:也是系统守护进程,它负责启动各运行层次特定的系统服务;所以很多进程的PPID是init,也负责收养孤儿进程。
cmd列中名字不带[]的普通守护进程(用户集守护进程)

图解如下:

======================================================

守护进程的开发: 

daemon()函数


直接借助daemon()函数完成。 daemon  -- 守护进程

头文件: 

#include <unistd.h>

函数原型: 
int daemon(int nochdir, int noclose);
函数参数:
nochdir:为0时表示将当前目录更改至“/”
noclose:为0时表示将标准输入、标准输出、标准错误重定向至“/dev/null”
返回值:
成功则返回0,失败返回-1

守护进程 和 后台进程区别

1. 守护进程和终端不挂钩;后台进程能往终端上输出东西(和终端挂钩);
2. 守护进程关闭终端时不受影响,守护进程不会随着终端的退出而退出;

额外补充:

//C 库函数 char *asctime(const struct tm *timeptr) 返回一个指向字符串的指针,它代表了结
构 struct timeptr 的日期和时间。
//C 库函数 struct tm *localtime(const time_t *timer) 使用 timer 的值来填充 tm 结构。
timer 的值被分解为 tm 结构,并用本地时区表示。
/*

对应的结构体:

struct tm {
int tm_sec; 秒,范围从 0 到 59
int tm_min; 分,范围从 0 到 59
int tm_hour; 小时,范围从 0 到 23
int tm_mday; 一月中的第几天,范围从 1 到 31
int tm_mon; 月份,范围从 0 到 11
int tm_year; 自 1900 起的年数
int tm_wday; 一周中的第几天,范围从 0 到 6
int tm_yday; 一年中的第几天,范围从 0 到 365
int tm_isdst; 夏令时
};

----------------------------------------------------------
注: bool  类型变量linux C不认识,需要包含 一个头文件: <stdbool.h>

exit(0),exit(1) 和 exit(-1)的区别

exit(0)表示程序正常退出;除了0之外,其他参数均代表程序异常退出,如:exit(1),exit(-1)。
exit(1)和exit(-1)是分别返回1和-1到主调程序。
exit(0)则是返回0。exit(0)表示程序正常退出,非0表示非正常退出。

log文件是记录系统活动信息的几个文件,通过它可以帮助快速定位问题,常见的有

----------------------------------------------------------


验证程序:

case1:  创建一个守护进程 , 创建/打开~目录下的daemon.log文件,每10s 写入一次系统时间到文件中

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include<stdbool.h>

static bool flag = true;

void handler(int sig) // 收到信号 后就 把 flag =false ---> 进入 main 里面的判断 -- 需要退出了
{
printf("I got a signal %d\nI'm quitting.\n", sig);
flag = false;
}

int main()
{
time_t t;
int fd;
//创建守护进程 int daemon(int nochdir, int noclose); -- 返回值 成功0 ,失败-1
// 第一个参数 -- nochdir:为0时表示将当前目录更改至“/”
// 第二个参数 -- noclose:为0时表示将标准输入、标准输出、标准错误重定向至“/dev/null”
if(-1 == daemon(0, 0))
{
printf("daemon error\n");
exit(1); //异常退出
}
//设置信号处理函数
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL)) // if 这个进程收到 SIGQUIT 信号,退出进程 exit(0) -正常退出
{
printf("sigaction error.\n");
exit(0);
}

//进程工作内容
while(flag) // flag  没收到退出信号的时候一直在执行 以下工作
{
fd = open("/home/orangepi/daemon.log", O_WRONLY | O_CREAT | O_APPEND,0644); // 在我们的~目录下创建daemon.log文件
if(fd == -1)
{
printf("open error\n");
}
t = time(0);
// asctime -- 把系统默认的时间类型 数据转换为 字符串类型的   localtime 拿到本地时间
char *buf = asctime(localtime(&t));
write(fd, buf, strlen(buf)); // 将获得时间数据写入文件
close(fd);
sleep(10);  // 每10s写入一次
}
return 0;
}

-------------------------------------------------------

 
 

 开机启动守护进程: 


sudo vi /etc/rc.local

里面添加我们创建的守护进程的 可执行文件 tdaemon , 不要用a.out, 使用 -o 改个名

/home/orangepi/hardwareSoft/udev/tdaemon

注意:  这里一定要用绝对路径,不要用相对路径,因为我们的守护进程会调到根目录,相对路径不一定能访问到

/home/orangepi/douyin/douyin /dev/ttyS5
/home/orangepi/douyin/shouhuDouyin

===================================================

需求:要求语音刷手机的程序一直保持运行,防止应用程序崩溃意外,先实现case2


case2 : 编写判断某进程是否在运行的程序:

  1 #include<stdio.h>
  2 #include<string.h>
  3
  4 int main()
  5 {
  6
  7     FILE *file;
  8     char buf[128]={'\0'};
  9     char *cmd="ps -elf | grep douyin | grep -v grep "; // 通过指令查看
 10     file = popen(cmd,"r");
 11     fgets(buf,128,file);
 12
 13     printf("buf: %s\n",buf);
 14     if(strstr(buf,"douyin")!=NULL){ // 子串没有进程名字,什么进程不运行
 15     puts("douyinPro is running");
 16     }
 17     else {
 18     puts("douyinPro is exited");
 19     }
 20
 21
 22     return 0;
 23 }


----------------------------------------
以popen的 方式去运行 cmd
同时,能把指令执行的结果拿到手

---------------------------------------------------------------------


case 3 :  守护进程实现 关不掉是 声控刷抖音

声控刷抖音代码参考这篇:串口小项目 - 声控刷抖音-****博客

/home/orangepi/douyin

注意:  守护进程会默认把 路口切换到根目录,执行cmd 的时候要给绝对路径

效果图: 

这里kill 程序,模拟程序崩溃

可以看到 我们的抖音进程 一直杀不死, 因为守护进程 一直在后台执行着
只要抖音程序一被关掉就会马上重新创建一个新的的进程

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include<stdbool.h>


int  judMent() // 这就是case2的 判断进程是否运行函数
{
    FILE *file;
    char buf[128]={'\0'};
    char *cmd="ps -elf | grep douynUnit | grep -v grep ";
    file = popen(cmd,"r");
    fgets(buf,128,file);

    printf("buf: %s\n",buf);
    if(strstr(buf,"douyin")!=NULL){
    return 0;
    }
    else {
    return -1;
    }

}


static bool flag = true; // 初始化为true -- 让main 中while一直执行


void handler(int sig) //收到对应信号后  退出
{
    printf("I got a signal %d\nI'm quitting.\n", sig);
    flag = false;
}
int main()
{
    time_t t;
    int fd;
    //创建守护进程
    if(-1 == daemon(0, 0))
    {
        printf("daemon error\n");
        exit(1);
    }
    //设置信号处理函数
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(sigaction(SIGQUIT, &act, NULL)) //收到信号SIGQUIT,执行act -这个槽(处理)函数
    {
        printf("sigaction error.\n");
        exit(0);
    }
    //进程工作内容
    while(flag)
    {
        if(judMent() == -1)//进程被杀死后再打开 -- 杀不死的进程
            fd = system("/home/orangepi/douyin/douyinUnit /dev/ttyS5 &");
       sleep(2);
    }
    return 0;
}

--------------------------------------------------------------------------------

UDEV 配置文件:

规则文件是 udev 里最重要的部分,默认是存放在 /etc/udev/rule.d/ 下。所有的规则文件必须以 ".rules"为后缀名


简单的规则举例:


KERNEL=="sda", NAME="my_root_disk", MODE="0660"


KERNEL 是匹配键,NAME 和 MODE 是赋值键。这条规则的意思是:如果有一个设备的内核名称为sda,则该条件生效,执行后面的赋值:在 /dev 下产生一个名为my_root_disk 的设备文件,并把设备文件的权限设为 0660。

查看设备详细信息

udevadm info --attribute-walk --name=/dev/设备名字

比如我的手机在这里:   /dev/bus/usb/001/003       -- 只需要把 = 后的改为这串即可

udevadm info --attribute-walk --name=/dev/bus/usb/001/003

查询到的信息可以写入设备的规则文件中,比如: 
    ATTRS{idProduct}=="0002"
    ATTRS{idVendor}=="1d6b"

//多乐两天匹配键 -- 来判断设备

如下: 


==================


udev 规则的匹配键:

ACTION:事件(uevent)的行为,例如:add(添加设备)、remove(删除设备);
KERNEL:内核设备名称,例如:sda,cdrom;
DEVPATH:设备的 devpath 路径;
SUBSYSTEM:设备的子系统名称,例如:sda 的系统为 block;
BUS:设备在 devpath 里的总线名称,例如:usb;
DRIVER:设备在 devpath 的设备驱动名称,例如:ide-cdrom;
ID:设备在 devpath 里的识别号;
SYSFS{filename}:设备的 devpath 路径下,设备的属性文件 "filename" 里的内容;

ENV{key}:环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键;
PROGRAM:调用外部命令;
RESULT:外部命令 PROGRAM 的返回结果。


===================================================、

U盘的自动挂载:

一般情况:(手动挂载)

一般U盘插入orangepi 需要挂载才能查看里面内容

// 将 u 盘挂载到 mnt 下,才能查看里面内容
sudo mount /dev/sda  /mnt/

然后 就可以 cd/mnt  去查看我们的 U盘 内容了

要取消挂载: sudo umount /mnt

也可以通过上面的指令来详细查看U盘数据: udevadm info --attribute-walk --name=/dev/设备名字
比如: U盘的匹配条件:

    KERNELS=="sdb"
    SUBSYSTEMS=="block"

配置规则文件, 实现自动挂载U盘


 在规则文件下(比如我们在/etc/udev/rule.d/下创建 usbBlock.rules ),加入:
支持挂载多个U盘:


ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", RUN{program}+="/bin/mkdir
/media/%k" ,RUN{program}+="/usr/bin/systemd-mount --no-block --collect $devnode
/media/%k"


 然后重启udev 服务即可:
sudo service udev restart

上一篇:玩转Virtual Box虚拟机


下一篇:HIVE:谓词下推