标签:management linux 管理工具 动态 udev
前言
在上篇中的内核模块管理讲解中,最后或多或少会留下一些疑问,那么这些疑问就是内核模块的参数是怎么和对应的硬件所匹配上的,而硬件又是怎么被内核识别,并且一个个都映射成实际存在的文件,而这些文件之间的关系及作用。当然在最后了解到,这些硬件设备的探测信息是通过一个叫udev的工具来实现的,通过udev中配置的规则可以很有效的识别每一个硬件,并配合sysfs文件系统,将每个探测到的硬件信息导入到/sys目录中,那么与/proc目录对与内核系统信息的映射悄悄互补,/sys目录中则专门存放于硬件相关的数据。
使用管理和探测设备属性信息的命令:udevadm
udevadm 命令
udev - Dynamic device management #动态设备管理工具
提示:一般情况下要设置udev命令规则会使用到udevadm命令来获取规则中需要设置的键的对应值,也就是设备的完整数据,而udev配置中恰好需要这,当然,下面介绍udevadm管理工具的一些基本常见用户,本篇主要以讲解udev内部的配置为主。
基本用法例子:
#先来查看当前系统识别了多少个磁盘
[root@mzf test]# fdisk -l | grep -o ‘^Disk /dev/[sh]d[a-z]\>‘ Disk /dev/sdb Disk /dev/sda
说明:一般情况下从CentOS6版本以后,无论IDE接口还是SCSI接口的都会被识别为sd*开头的设备名称。这里显示目录系统上有两块磁盘。
当然如果需要再次插入一块磁盘,可能会识别为sdc,当然这也不一定。因为这要取决于udev中默认配置。那么就查看第一个磁盘/dev/sda 的信息吧。
设备信相关信息的查看
#显示/dev/sda磁盘路径硬件点
[root@mzf test]# udevadm info -q path -n /dev/sda /devices/pci0000:00/0000:00:10.0/host2/target2:0:0/2:0:0:0/block/sda
说明:/dev/sda难道不是本身的路径吗?一般需要挂载的时候常用的磁盘设备基本都是块设备文件,因为linux内核对文件进行了分类,而这些块设备文件为了便于识别,每个快设备都会有相应的主设备号、次设备号,因此对其操作直接指定对应的块设备文件路径。而设备只是提供访问,输出信息当然不行。
#显示于设备相关的完整信息,包括其父级parent device设备
[root@mzf myusb]# udevadm info -a -p $(udevadm info -q path -n /dev/sda)
解析:这里会显示所有与磁盘相关的信息,比如磁盘本身的属性,磁盘的设备类型等,在下面会列出所有parent device列表属性,这些都是此磁盘的父级或上级设备,比如磁盘的接口点,磁盘的标记,磁盘接口的主板插槽等,关于设备的上下级下面会讲。
提示:当然如果不知道设备本身的设备数据存放路径,也可以通过-name或-n选项来指设备命令进行特定的设备属性查询,入下:
#只显示设备本身的完整信息
[root@localhost ~]# udevadm info --query=property --name=/dev/sda
解析:这里/dev/sda作为一个标准的块设备,其主设备号、此设备号等信息完整显示,而且在下面显示其属性的连接路径。这里可以发现其为块设备,那么其对其的块设备等信息都是按照设备的设备号去建立的连接路径,而不是完全安装设备名。
列出设备的具体属性
udevadm info -a -p [device name] #这里的device name表示目前识别的规则NAME
例如:查看video设备,注意这里的路径是已经指定的数据存放目录
udevadm info -a -p /sys/class/backlight/acpi_video0
加载新规则前测试规则
udevadm test $(udevadm info -q path -n [device name]) 2>&1
例如:主要检查配置的语法以及匹配对应检测硬件
udevadm test /sys/class/bakcklight/acpi_video0/
加载udev新规则
方法1:规则自动重载识别
udevadm control --reload
方法2:自动重载可能失败,入股失败可以手工强制触发规则
udevadm tigger
检查新的eSATA (磁盘)设备
#如果在平时插入的磁盘而没有被内核识别,那么可以使用下面里的命名:
echo 0 0 0 | tee /sys/class/scsi_host/host*/scan echo “- - -” > /sys/class/csci_host/host*/scan
解析:host目录名一般根数字,一般为host[0-9]这里显示为scsi接口下的热存储设备,每个磁盘都会在一个热存储口下。
#也可以通过安装scsiaddAUR(本身,此工具为专门解决新磁盘设备重新检查)并尝试命令
scsiadd -s
#希望你设备现在/dev设备里,如果不是,你可以试试上面的命令在运行
udevadm moitor
Udev浅谈
前面介绍了udevadm命令,好像具体和开始说的udev本身概率没有什么关系,下面来谈一谈来由。在kernel2.4之前,Linux内核中采用devfs文件系统机制,使用了传统意义上的是静态设备创建方法,及在/dev目录下不断的创建大量设备节点,且不管相应的设备是否真的存在,我想可能是因为预期越越多识别问题就会少吧。关于设备节点的概率这里用一个命令来表示:
#使用mknod创建一个块(b)设备和字符(c)设备
[root@mzf test]# mknod myblock b 10 1 [root@mzf test]# mknod mychar c 12 2
#查看其设备的主设备号和次设备号
[root@mzf test]# ls -l /test/my* brw-r--r--. 1 root root 10, 1 Aug 22 05:28 /test/myblock crw-r--r--. 1 root root 12, 2 Aug 22 05:28 /test/mychar
解析:虽然设备文件只有设备号,但是不断调用mknod命令多少存在一些问题,及每个设备文件不占用磁盘空间,但是却占用了一个inode节点,这样即使很少的设备,也会有一些完全用不上的inode节点被占用,并且管理和查找变得没有具体的分类。
解决静态的大量无脑创建
当然,在kernel2.4以后为了改变这种静态调用mknod命令的预留创建方法及devfs机制,出现了一种新的文件系统sysfs文件系统,这种机制会在内核加载和启动时自动把识别到的设备信息导入到/sys目录下,并且随之的/dev目录下的设备节点也会随之动态改变。当然,因为识别机制并创建阶段是有一定的先后顺序,而且这些顺序并不能手动设定,很肯定是随机的。下面用一个例子说明:
开始磁盘有一个磁盘,并且设备节点被自动创建为/dev/sda,那么如果加载了一个设备,经过重新扫描后,可能会使/dev/sdb,但是在一次重启时,之前的/dev/sdb设备本身可能因为一些不确定特性而被优先识别,那么进入系统之后,可能会发现,/dev/sda变成了/dev/sdb,而/dev/sdb才是最开始使用的磁盘设备。在这时肯定想如果手动自定节点的命令规则就好了。
设备规则自我掌控
sysfs只是把之前devfs在系统内存空间中执行的过程一部分放到用户空间中使用,而调用的机制也只是devfs内部的机制,最重要的设备的检测、创建、命名一切变得动态。但是只有sysfs自身才能控制。而用户想要自己有个独立或者稳定的设备节点,同时又不会大规模的创建,甚至是用到才会自动创建节点。这时udev机制出现,通过自身的程序与配置,来将匹配内部预留机制的设备通过事件检查识别硬件设备,然后将这些设备信息再导入到sysfs文件系统中,并根据符号的属性及对应的设备号等,最后在/dev目录下创建归档的节点名称,当然这些只是预留的配置,需要更改,只需要添加配置。
udev规则的的作用:
1、重命名设备节点的缺省名字为其他名字
2、通过创建符号链接到缺省设备阶段来提供一个可选的固定设备节点命令
3、基于程序的输出命令设备节点
4、改变设备节点的权限和所有权
5、但设备节点被创建或删除时(通常是添加设备或拔出设备时)执行的一个脚本
6、重命令网络接口
内置固定名方法:
一开始安装了udev后,当然是没有任何配置的,但udev为系统外的一些设备提供了固定命名,即使不用自定义任何规则配置,也会有一些默认的内置配置。udev为存储设备提供了系统外命令方法,这些命令的文件联系保存在/dev/disk目录下。
使用命令 ls -lR /dev/disk 可以查看,其中by-label目录下就有明显的名称显示,例如:
[root@mzf ~]# ls /dev/disk/ by-id by-label by-path by-uuid
解析:这些目录下的文件都是链接文件,这里的id、label、path、by-uuid都是与其设备相对于的具体的设备规则属性的值。
规则的定义与书写:
提示:
1、一般默认都会保存在 /etc/udev/rules.c/目录下并以filename.rules命令,必须是.rules结尾,不然udev机制无法识。
2、在安装的新版本的udev可能会有一些规则案例,比如在/etc/udev/50.udev.rules文件下,可以用来参考。
3、在每一个规则文件中,以 # 或者 ; 开头的行都被认为是注释,每一个非空白行都是一条规则,规则必须在一行写完,不能换行或跨行。
4 、一个设备是可以被多条规则同时匹配的,因此可以利用这点来创建多个命令节点,设置NAME,因此就可以有多个可选节点可以创建。
语法规则:
每条规则都一系列的键值对来创建,这些键值对通过逗号分隔,键分为匹配键和赋值键,当此条匹配键的逐个被匹配后,将使用赋值键使用的赋值用来创建对应的命令节点或者链接,然后进行下一条进行处理,每条规则至少有一个匹配件和赋值键。
匹配键和赋值键:
举例说明:
KERNEL=="sdc",NAME="my_usb_disk"
解析:这里有一个匹配键及KERNEL为sdc的设备,还有一个赋值键(NAME),及节点命令设置为my_usb_disk。
注意:
1、这里匹配键通常使用key==value,及键与值之间使用==及两个等号连接,而赋值键则要使用一个=号表示对其赋值,有点类似于shell或常见变成语言中的匹配字符或对象相对以及对变量或对象赋值。
2、再次强调每一行的为一条完整的规则配置,不能使用平时在shell脚步里或配置文件中的 \ 来续行,也不要进行换行,必须要写为一行,不然就会认为是分开的两条或多条规则。
常见的基本的规则键值:
匹配键
KERNEL - #匹配系统定义s的设备在内置中的名称,比如默认定义的sda、sdb2等 SUBSYSTEM - #匹配设备的子系统,比如block等 DRIVER - #匹配设备支持的驱动名称
提示:
1、每一个匹配键都会决定着匹配的设备,如果一个设备有很多设备,有可变的有不可变的(可能在某些环境下),那么就可以直接设置为多个匹配键进行,然后再赋值键的值设置为相同,这样可以确保这个设备的节点名会更加稳定。
2、但是如果有多个设备有很多相同的属性特征,这时就可以进行设置极少的相同匹配键,并使用更多的不同的属性匹配键来匹配,同样设置多行,这样匹配时就可以不同的赋值键的值来区分不同的设备。
赋值键
NAME - #根据匹配键找出对应的设备,并对其进行进行设置命令节点,也就是设备应该使用的名称 SYMLINK - #一个设备节点的可选名字的符号链接
提示:
无论是自己配置规则还是不写,被识别到的设备都会被udev创建一个命令节点,这时如果再次设置条目节点,那么就会覆盖掉之前的节点名。如果想要有多个不同节点名来访问设备,可以使用SYMLINK+="aa bb cc",这样可以对设备创建符号链接名称,同样使用这些名称一样可以访问和使用此设备,注意,多个要是使用+=符号,且里面的多个值使用空格隔开。
例如下列几条规则配置:
KERNEL=="sdc",NAME="my_usb_disk"
解析:匹配设备名为sdc的设备,并重新设置其设备名为my_usb_disk,访问此设备节点为/dev/my_usb_disk
KERNEL=="sdc",DRIVER=="usb-storage",SYMLINK+="myusb_link"
解析:匹配设备名为sdc,且驱动为usb-storage的设备,添加一个符号链接命名为myusb_link,并对现有的命令节点进行链接,没有指定NAME赋值键及设备名里的定义,设备使用默认缺省的设备来命令设备名,默认不写NAME为/dev/sdc,及对/dev/sdc创建符号链接。需要使用此符号链接名访问设备,则访问/dev/myusb_link。
KERNEL=="sdc",SYSMLINK+="myusb myusb0"
解析:匹配设备名为sdc,并追加两个设备符号链接名各为myusb、myusb0。同样指向的是匹配sdc的设备及/dev/sdc。
关于命名连接和命名节点的配置注意:
命名链接:可以有多个,比较类似符号链接文件而已。
命名节点:只能有一个,如果指定了命令节点,且原有的节点默认将被覆盖掉。如:
对/dev/sdc1 的信息来配置一条规则,NAME="my_usb_disk",那么最后/dev下只会显示/dev/my_usb_disk,而继承原来/dev/sdc1块设备文件的主设备号、次设备号,相当于直接提供了系统与硬件本身的一个联络点名称。
注意:再次强调,无论使用NAME还是SYSMLINK,最后都必须要至少设置一个。
高级规则匹配(sysfs属性)
基本匹配键无法精准对多个硬件类型相同的设备设备进行过滤匹配,因此要更精准的匹配,可以通过硬件的一些高级属性信息来完成,如硬件的供应商编号、厂商编号、序列号、存储能力、分区数等一些硬件驱动会自动将这些属性信息存放到sysfs文件系统中,可以使用udev规则中的ATTR匹配键来进行匹配。
配置规则例子:
SUBSYSTEM=="block",ATTR{size}=="30310400",SYTEMLINK+="myusb2"
解析:此规则匹配为设备子系统类型为block及块设备,然后此设备的size及大小属性为30310400,这些熟悉可以通过命令查看,比如,要匹配sdc1的属性,可以查看其属性:
udevadm info -a -p $( udevadm info -q path -n /dev/sdc2)
设置级联匹配
linux内核对于设备的展示实际上是有类似树状的结构展示,这个信息同样通过sysfs来提供,所谓的级联在很多领域都会用到,一般最会联想到的就是数据库中的表与表之间的关联,及单个或多个主表、从表之间的关系,不过数据库表的级联关系相对来说比较复杂。那么udev来使用级联连映射各个硬件设备同样也是按照这种思路,比如一个磁盘设备节点为一个SCSI硬盘的子级,而SCSI硬盘又是一个SCSI控制器的子级,那么此磁盘设备有了分区后,对应的分区则也属于此磁盘设备节点的子级。
简要理解:级联关系及一个设备于其父级相关设备的关系。
在上面使用的几个基本匹配键及(KERNEL SUBSYSTEMS DRIVERS ATTRS)中,此匹配键只是对其匹配的唯一设备属性做匹配,而不是对其配备到的级联关系的其它设备同时做匹配,因此下面 udev 提供了用于级联的匹配键:
级联匹配键:
KERNELS - #匹配设备及其所有父设备的内核名称 SUBSYSTEMS - #匹配设备及其所有父设备的内核名称 DRIVERS - #匹配设备及其所有父设备的驱动名称 ATTRS - #匹配设备及其所有父设备在sysfs中的属性值。如果指定了多个ATTRS匹配,那么必须在同一设备上全部匹配成功,才算最终匹配成功。
注意:属性值中的尾部空白会被忽略,除非指定的值就包含尾部空白。
字符串替换:可用于类似bash中的glob风格通配符
* - #匹配任意数字的字符,包括0次 ? - #匹配单独一个字符 [] - #匹配括号内任意一个字符 | - #用于分隔两个可互相替代的匹配模式,如"ab|x*",则匹配ax或者x*(及x开头的字符)
使用配分符模糊匹配:
例子1:
KERNEL=="[hs]dc[1-9]",SUBSYSTEM=="block",DRIVER=="usb",SYMLINK+="myusb_%n"
解析:匹配设备名为sdc或hdc,后面跟一个非0数字,比如sdc1、hdc2,但是要是block及块设备,并且驱动类型为usb的设备,然后设置一个符号设备连接节点为myusb_dev后跟设备内核名称,比如KERNEL=sdc1,那么链接点为/dev/myusb_sdc1
例子2:
KERNEL=="video*",SUBSYSTEM=="usb",SYSTELINK+="usb_%k"
解析:匹配一个开头为video后面跟任意个字符的设备名,并且为usb子系统的设备,创建一个符号设备链接,比如为/dev/usb_video-2。
例子3:
KERNEL=="fd[0-9]",NAME=="floppy/%n",SYMLINK+="%k"
解析: 匹配fd开头后面一个数字,一般为软盘,然后其节点路径放置在/dev/floppy/目录下,例如/dev/floppy/fd1,并且对此NAME节点设备创建一个符号链接节点为/dev/fd1。
高级特殊属性sysfs
磁盘的直接命令节点路径大多会选择在/dev,一般最常见的为块文件,在/dev目录下一般都是一个设备的命令节点表示或者链接节点表示,一般在系统启动过程中加载所有的文件系统驱动以及所需模块之后,udev机制会通过驱动模块驱动对应的硬件,然后得到每个硬件设备的相应的属性信息,最终这些设备信息被存放内存中导入到sysfs文件系统中。
例子:查看/sys/下结构
#/sys/将于已经识别的硬件相关信息保存到/sys/目录中
[root@mzf ~]# ls /sys/ block bus class dev devices firmware fs hypervisor kernel module power
#查看size属性
[root@mzf ~]# cat /sys/block/sda/size 41943040
例子:如上述如果要对sda做一个精确匹配udev规则,可以根据其size属性配置规则:
KERNEL="sda",ATTR{size}="41943040",SYMLINK+="scsi_sda"
说明:详细的属性信息会在对于的/sys/block/dev_name下有各种说明,但这里指的是block设备,当然如果有其它设备需要再/sys下找相应的SUBSYSTEM属性来确定,当然也有命令可以来帮助实现如,查/dev/sda设备属性:
例子:获取更多的指定设备信息
#查看所有与/dev/sda其相关的设备的属性
[root@mzf ~]# udevadm info --query=property --name=/dev/sda
#只查询属性正在对应的设备/dev/sda1设备数据存放路径
[root@mzf ~]# udevadm info --query=path --name=/dev/sda1 /devices/pci0000:00/0000:00:10.0/host2/target2:0:0/2:0:0:0/block/sda/sda1
#显示更多的样例格式属性
[root@mzf ~]# udevadm info -a -p `udevadm info --query=path --name=/dev/sda1`
注意:/dev/sda不是设备信息的存放路径,而是一个设备的命令节点也就是设备的个体表示具体的相关设备信息会存放在/sys/devices/device_nam或 /sys/block/device_name 下,这里才是sysfs文件系统真正导入的硬件信息存放路径。
简单小案例:
usb flash设备,如U盘、读卡器
注:因为这些设备一般会存储数据,所以一般会有文件系统,那么如果是设备插入后没有很好的识别,很有可能会是当前系统内核模块没有对此文件系统的支持,因此需要先加载设备需要的文件系统模块。可使用下列方式来解决:
准备工作:
1、lsmod 或 lsusb来查看是否已经有接口被新设备使用,如果没有就手动加载驱动
[root@mzf ~]# lsmod | grep ‘driver_name‘
2、查看当前系统支持的文件系统列表
[root@mzf ~]# cat /proc/filesystems
注意:如果发现没有加载对应设备的文件系统,可以先加载对应的文件系统模块文件:
[root@mzf ~]# modprobe mod_name
解析:mod_name一般存放在对于版本系统版本的模块文件存放路径,比如/usr/lib/modules/3.2.89/kernel/下有对应模块的模块,而文件系统模块一般在fs目录下。
注意:有时候可能modprobe并不能识别到模块名,可能因为对应的配置文件没有保存/etc/modules.d/*.conf配置,也可能是/usr/lib//modules/$(uname -r)/modules.dep已经其次各种配置中未读取,此系统版本的模块路径,可以使用下面命令重新生成模块模块依赖配置:
[root@mzf ~]# depmod -a
3、再次使用下面命令来查看模块是否加载
lsmod mod_name | grep ‘\<mod_name\>‘ cat /proc/modules | grep ‘\<mod_name\>‘
注意:可能任然不行,那么在编译内核模块时,那么可以通过/user/lib/modules/$(uname -r)/kernel/fs/查找需要的文件系统支持模块,然后使用insmod 加载,如fat格式U盘:
insmod /usr/lib/modules/$(uname -r)/kernel/fs/fat.ko insmod /usr/lib/modules/$(uname -r)/kernel/fs/vfat.ko
提示:具体的模块问题处理在上一篇有也案例以及讲解,这里以udev为主要所以不再做过多说明。
具体规则的配置:
#在设备被识别后,可以通过lsusb命令来查看一些唯一性的属性,来接下来的匹配做准备
[root@mzf ~]# lsusb -v | grep -A 5 ‘\<idVendor\>‘ -- idVendor 0x1d6b Linux Foundation idProduct 0x0001 1.1 root hub bcdDevice 2.06 iManufacturer 3 Linux 2.6.32-642.el6.x86_64 uhci_hcd iProduct 2 UHCI Host Controller iSerial 1 0000:02:00.0
说明:idVendor为出厂号,而idProduct为产品号,一般电子产品此项都会唯一。
#usb接口的磁盘设备一般会被识别为/dev/sd开头的设备,所有可以通过此kernel设备命名来匹配此设备:
KERNEL=="sd*",SUBSYSTEM=="block",ATTR{ipVendor}=="4a3gb3",ATTR{idProduct}=="445566",name=="/myusb/usb_%n" KERNEL=="sdc1",SUBSYSTEM=="block",ATTR{size}=="30310400",SYMLINK+="usb" KERNEL=="sdc[0-9]",SUBSYSTEM=="block",SUBSYSTEMS=="scsi",ATTRS{model}=="USB Flash Drive ",NAME="my_usb_disk"
#在配置完成后可以手动强制扫描udev配置
[root@mzf ~]# udevadm trigger
#然后在/dev/目录查看或搜索匹配的设备是否已经被配置命令
[root@mzf ~]# ls /dev [root@mzf ~]# find /dev/ -name "*dev_name*"
多媒体设备
假设有有很多的媒体设备,比如usb的外置摄像头、播放器、智能手机、mb4等。可以通过其设备的子系统类型、厂商编号、以及其对应的商品唯一发行编号的精良具有唯一性的设备属性信息来匹配多个设备。
具体规则配置:
新建一个配置规则文件/etc/udev/rules.d/69-webcam.rules,配置如下:
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="05a9", ATTRS{idProduct}=="4519", SYMLINK+="video-cam1" KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="08f6", SYMLINK+="video-cam2" KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0840", SYMLINK+="video-cam3"
提示:注意每一行都是一条规则,不要续行,否则会导致匹配出错或者直接导致udev读取配置格式出错。
配置网卡设备命令
#显示对应网卡的具体硬件所有相关信息
[root@mzf ~]# udevadm info -a -p /sys/class/net/eth0
#只查看此网卡具体相关信息
[root@mzf ~]# udevadm info -a -p /sys/class/net/eth0 | sed -n ‘/eth0/,/^[[:space:]]*$/p‘ looking at device ‘/devices/pci0000:00/0000:00:11.0/0000:02:01.0/net/eth0‘: KERNEL=="eth0" SUBSYSTEM=="net" DRIVER=="" ATTR{addr_assign_type}=="0" ATTR{addr_len}=="6" ATTR{dev_id}=="0x0" ATTR{ifalias}=="" ATTR{iflink}=="2" ATTR{ifindex}=="2" ATTR{features}=="0x14b89" ATTR{type}=="1" ATTR{link_mode}=="0" ATTR{address}=="00:0c:29:a3:5d:d7"
解析:上面可以看到address表示网卡硬件地址,及常说的MAC的地址,此地址通过6段16进制数组成,有唯一性。
#根据上面来配置命令,比如eth0改为game_lan,表示玩游戏专用于连接外网的网卡设备。
KERNEL=="eth*",SUBSYSTEM=="net",ATTR{type}=="1",ATTR{address}=="00:0c:29:a3:5d:d7",name="game_lan"
补充:
默认的/etc/udev/rules.d/70-persistent-net.rules网卡命令规则配置文件例子:
# PCI device 0x8086:0x100f (e1000) SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:0c:29:a3:5d:d7", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
解析:这里涉及到了ACTION动作,add表示及设备连接时会自动检测设备并设置访问节点。怪不得有时候查上网卡添加配置文件就会自动识别,是会自动添加的。
本文出自 “孟天霸-IT的垃圾回收站” 博客,谢绝转载!
标签:management linux 管理工具 动态 udev
原文地址:http://mengzhaofu.blog.51cto.com/10085198/1856428