高级DBA运维
成长之路

第9章:系统用户管理权限说明

文章目录

1.  解决几个常见面试题

—1.1 系统启动流程

centos6启动流程: (采用串行模式启动系统)
01. 按下服务电源
02. 开机自检 检查硬件信息是否存在问题
03. MBR引导 可以加载磁盘引导系统启动(磁盘管理)
04. GRUB菜单 可以选择使用的内核信息
            可以设置是否采用单用户模式进入系统 修复系统 重置密码
05. 加载内核信息 可以更好控制管理硬件设备
06. 运行系统的第一个进程 init进程 -- 控制后续进程依次启动
07. 读取/etc/inittab 识别系统的运行级别
08. 执行/etc/rc.d/rc.sysinit脚本 初始化主机名信息 初始化网卡信息
09. 执行/etc/rc.d/rc脚本 让开机自动运行的服务可以按顺序运行起来
10. 运行最后一个进程mingetty进程 可以看登陆提示信息

centos7启动流程: (采用并行模式启动系统)
01. 按下服务电源
02. 开机自检 检查硬件信息是否存在问题
03. MBR引导 可以加载磁盘引导系统启动(磁盘管理)
04. GRUB菜单 可以选择使用的内核信息
            可以设置是否采用单用户模式进入系统 修复系统 重置密码
05. 加载内核信息 可以更好控制管理硬件设备
06. 运行系统的第一个进程 systemd进程 -- 控制后续进程并行启动
07. 识别加载系统运行级别(target) /etc/systemd/system/default.target
08. 完成系统初始化过程 加载初始化target文件
                   /usr/lib/systemd/system/sysinit.target
09. 让服务可以开机自动运行 加载此/etc/systemd/system目录中文件实现服务开机自动运行
10. 运行最后一个进程mingetty进程 可以看登陆提示信息

—1.2  如何让命令或服务可以开机自动运行

centos6
1) 将命令信息放入到 /etc/rc.local(脚本)
2) 利用chkconfig可以控制服务是否开机运行
根据运行级别进行进行控制

centos7
1) 将命令信息放入到 /etc/rc.local(脚本)
PS: 如果想让rc.local文件起作用,需要添加执行权限
[root@linux ~]# ll /etc/rc.d/rc.local 
-rw-r--r--. 1 root root 473 Oct 31 2018 /etc/rc.d/rc.local
[root@linux ~]# chmod +x /etc/rc.d/rc.local
[root@linux ~]# ll /etc/rc.d/rc.local 
-rwxr-xr-x. 1 root root 473 Oct 31 2018 /etc/rc.d/rc.local
2) 利用systemctl enable/disable 服务名称

1.3 如何强制修改系统用户密码

2. 用户管理相关知识

—2.1 系统中用户分类

超级管理员用户 root 
uid=0
系统虚拟用户 nobody 
uid=1-999 centos7 不需要有家目录/不需要登陆系统
uid=1-499 centos6
系统普通用户 
uid=1000+ centos7
uid=500+ centos6

—2.2 用户和组的关系

一个用户属于一个组 1对1关系
一个用户属于多个组 1对多关系
多个用户属于多个组 多对多关系
多个用户属于一个组 多对1关系

3. 用户管理中重要文件信息

—3.1 用户相关的文件

——3.1.1 /etc/passwd 用户信息记录文件

(一定要保存好,别删别改)
[root@linux ~]# ll /etc/passwd --- 用户信息记录文件
-rw-r--r-- 1 root root 2103 Feb 4 2019 /etc/passwd
[root@linux ~]# cat /etc/passwd
oldboy01:x:1003:1003:sa-xiaozhang:/home/oldboy01:/bin/bash
oldboy02:x:1004:1003:dev-xiaoli:/home/oldboy02:/bin/bash
oldboy03:x:1005:1005:eduer-oldboy03:/home/oldboy03:/bin/bash
01 02 03 04 05 06 07

第一列: 用户名称信息
第二列: 表示用户密码信息. 真正密码信息已经保存在了/etc/shadow文件中
第三列: 表示用户uid数值信息
第四列: 表示用户gid数值信息
第五列: 表示用户注释信息
第六列: 定义用户所在家目录信息
第七列: 用户是否能够登陆系统 shell信息(/etc/shells)
shell信息哪些设置可以进行远程登陆
/bin/bash
/bin/sh
/usr/bin/bash
/usr/bin/sh
shell信息哪些设置不可以进行远程登陆
/sbin/nologin
/usr/sbin/nologin

——3.1.2 /etc/shadow 用户密码记录文件

/etc/shadow --- 用户密码信息记录文件
[root@linux ~]# ll /etc/shadow
---------- 1 root root 3459 Jan 16 09:54 /etc/shadow
[root@linux ~]# cat /etc/shadow
root:$6$VGLWbFLYBH6sHvNo$V.jSAS/jV.ADhnH1G/M9qohezP.yHCHGrOFBRBzICqsoeJV6HZFFkREJXVMai2tGXEQZQyikYAknVGErxiWEF.:
:0:99999:7:::
bin:*:17834:0:99999:7:::

—3.2 用户组相关的文件

/etc/group --- 确认组是否存在,可以查看group文件
/etc/gshadow

—3.3 用户管理重要目录/etc/skel/

普通用户家目录 --- 房子
/etc/skel --- 样板房
[root@linux ~]# ll -d /etc/skel/
drwxr-xr-x. 2 root root 62 Apr 11 2018 /etc/skel/

企业应用:
如何让新用户家目录中,有企业注意事项文件存在
第一个历程: 进入到/etc/skel目录中, 添加文件信息
cd /etc/skel

第二个历程: 编辑文件
[root@linux skel]# echo "info oldboy" >./readme

第三个历程: 创建用户, 并切换到指定用户下, 检查家目录中的数据信息
[root@linux ~]# useradd oldboy21
[root@linux ~]# su - oldboy21
[oldboy21@linux ~]$ ll
total 4
-rw-r--r-- 1 oldboy21 oldboy21 12 Jan 23 17:34 readme
[oldboy21@linux ~]$ cat readme
info oldboy

结论: 每次创建一个用户的时候,都会将/etc/skel目录中的内容复制到用户家目录下
[oldboy21@linux ~]$ ll -a
total 20
drwx------ 2 oldboy21 oldboy21 76 Jan 23 17:35 .
drwxr-xr-x. 26 root root 4096 Jan 23 17:35 ..
-rw-r--r-- 1 oldboy21 oldboy21 18 Apr 11 2018 .bash_logout  表示记录系统退出执行的命令信息
-rw-r--r-- 1 oldboy21 oldboy21 193 Apr 11 2018 .bash_profile 表示记录环境变量和别名信息文件
-rw-r--r-- 1 oldboy21 oldboy21 231 Apr 11 2018 .bashrc 表示记录系统默认别名信息的文件
-rw-r--r-- 1 oldboy21 oldboy21 12 Jan 23 17:34 readme
[oldboy21@linux ~]$ ll -a /etc/skel/
total 28
drwxr-xr-x. 2 root root 76 Jan 23 17:34 .
drwxr-xr-x. 83 root root 8192 Jan 23 17:35 ..
-rw-r--r--. 1 root root 18 Apr 11 2018 .bash_logout
-rw-r--r--. 1 root root 193 Apr 11 2018 .bash_profile
-rw-r--r--. 1 root root 231 Apr 11 2018 .bashrc
-rw-r--r-- 1 root root 12 Jan 23 17:34 readme

企业异常案例:
当家目录信息被清空之后, 命令提示符会发生变化
-bash-4.2$
解决方式:
将/etc/skel目录中的数据信息复制到相应用户家目录中
cp -a /etc/skel/.bash* ~/
说明: centos7.5中有时不会出现提示符变化情况

4. 用户管理相关命令

—4.1 useradd 用于创建用户

只有root用户有能力创建新用户(或者...)
相关参数总结:
-u 指定新建用户的uid数值
-G 指定用户还属于哪个组 指定用户附属组信息
-g 指定用户属于主要组信息
-s 指定新建用户shell信息, 如果指定为/sbin/nologin,用户无法登陆系统
-M 创建新用户时,不要给家目录
-c 创建的新用户添加注释信息

—4.2 id检查用户是否存在

id oldboy051
uid=1024(oldboy051) gid=1024(oldboy051) groups=1024(oldboy051)
用户的uid信息 用户的组id,所属组 用户所属的其他组信息
所属主要组

—4.3 如何设置uid值

如何设置所属组 666 同时还属于1024这个组
useradd -u 666 oldboy30 -G oldboy051
[root@linux ~]# id oldboy30
uid=666(oldboy30) gid=1025(oldboy30) groups=1025(oldboy30),1024(oldboy051)

创建一个用户alex999指定uid为888,禁止用户登录系统,不创建家目录
useradd alex999 -u 888 -s /sbin/nologin -M --- 创建虚拟用户方法
虚拟用户主要用于管理服务进程

—4.4 userdel 删除用户信息

userdel xiadao --- 直接利用命令删除用户,会有用户的残留信息(家目录还有 用户邮件还有)
userdel -r 用户 --- 彻底删除用户信息

彻底删除用户的方法:
[root@linux ~]# userdel -r xiadao01
[root@linux ~]# userdel -r xiadao01
userdel: user 'xiadao01' does not exist
[root@linux ~]#  ll -d /home/xiadao01
ls: cannot access /home/xiadao01: No such file or directory
[root@linux ~]#  ll /var/mail/xiadao01
ls: cannot access /var/mail/xiadao01: No such file or directory
[root@linux ~]#  useradd xiadao01

如果删除用户时,有残留信息没有删除掉如何解决:
1) 手动删除残留数据
2) 创建出一个相同的用户,再次删除
   第一个历程: 创建用户
   useradd -u uid值 相同用户名 --- uid值和家目录属主信息保持一致
   第二个历程: 彻底删除用户
   userdel -r 用户名

—4.5 usermod 修改用户信息

-u 指定新建用户的uid数值
-G 指定用户还属于哪个组 指定用户附属组信息
-g 指定用户属于主要组信息
-s 指定新建用户shell信息, 如果指定为/sbin/nologin,用户无法登陆系统
-c 创建的新用户添加注释信息

—4.6  passwd 设置用户密码信息

——4.6.1 超级管理员设置用户密码

1)交互式设置用户密码
  passwd oldboy 
  交互输入密码信息
2)免交互设置用户密码
  echo 123456|passwd --stdin oldboy

——4.6.2 普通用户设置密码

交互式给自己设置密码
passwd
交互设置密码

——4.6.3 在企业中如何给服务器主机设置密码

01)密码要复杂12位以上字母数字及特殊符号
    123456654321!QAZX2wsx
    3.14159265324234234123
02)保存好密码信息
    a 在execl记录保存
    b 利用专业密码保存工具进行保存
      离线本地: keepss
      网站在线: 
    c 大企业用户和密码统一管理(相当于活动目录AD)
    d 动态密码:动态口令,第三方提供自己开发也很简单。

—4.7 groupadd 创建用户组信息

-g 设置用户组的id信息
[root@linux ~]# grep oldboy002 /etc/group
oldboy002:x:1002:
[root@linux ~]# groupadd oldboy003 -g 1003
[root@linux ~]# grep oldboy003 /etc/group
oldboy003:x:1003:

—4.8 查询相关命令

1)id 确认用户的信息(属主uid 主要属组gid 附属组信息)
2)w 显示主机工作时间 负载情况 连接主机的用户信息  CPU平均负载情况(核数):1分钟,5分钟,15分钟
   [root@linux ~]# w
   16:40:32 up 1:41, 2 users, load average: 0.02, 0.04, 0.05
   USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
   root tty1 19:37 11.00s 0.02s 0.02s -bash
   root pts/0 10.0.0.1 15:07 0.00s 1.81s 0.00s w
   oldboy pts/2 10.0.0.1 16:26 13:44 0.03s 0.03s -bash
3)last 哪个用户在什么时候 从哪里 远程登录你的系统 用户登录的信息
4)lastlog 显示linux中所有用户最近一次远程登录的信息
5)top (顶替的命令uptime、free -h、ps -ef)
   M按照内存使用率排序
   P按照cpu使用率进行排序
6)ps aux cpu与内存的使用率
   sort -rnk2
   增加sort -h参数
7)htop #增强版本的top命令
8)iotop #显示系统中每个进程使用的磁盘io
9)iftop #显示系统网络流量 

5. 权限管理部分

—5.1 普通用户可以像root用户一样操作系统

一般普通用户管理系统时会受到限制,解决如何不会限制普通用户操作

1) 篡权夺位
   su - root/su -(不是很安全,root权限泛滥)
2) 文件或目录的权限设置
   a 修改文件属主或属组信息 chown
     chown 属主名.属组名 文件路径信息
   b 修改文件权限信息 chmod
     chmod a+w 文件的路径信息
   缺点:所有用户都可能对文件数据拥有权限
3)sudo授权
  缺点:需要根据不同的用户进行授权
4)使用特殊权限位进行授权
  特点:root可以执行命令,指定某个命令所有用户都可以操作
  比如/bin/root此命令所有用户都可以像root一样操作,不需要输入sudo

—5.2 文件或目录的详细权限说明

——5.2.1 权限说明

文件或目录总共有 9位权限

——5.2.1 文件 (更关注是否有读权限)

读权限: 查看文件能力
写权限: 编辑文件能力
执行权限: 执行权限

结论01: 只要文件有执行权限, root用户对于这个文件就属于无敌的存在
结论02: 如果想让所有用户都有文件执行能力,必须先有对文件读取能力
结论03: 如果想让所有用户都有文件写入能力,必须先有对文件读取能力
结论04: 如果想让文件写权限和执行权限起作用, 必须先有读取权限

——5.2.2 目录 (更关注是否有执行权限)

读权限: 是否能查看目录中数据属性信息 数据名称信息
写权限: 是否可以在目录中创建或删除数据信息
执行权限: 是否可以进入到目录中

结论01: 对于目录文件而言, root用户对于这个文件就属于无敌的存在
结论02: 如果想让目录有读取能力, 必须先有执行权限
结论03: 如果想让目录有写入能力, 必须现有执行权限
结论04: 如果想让目录写权限和读权限起作用, 必须先有执行权限

终极结论: 文件或目录的权限具有继承关系

——5.2.3 用户访问文件的原理

—5.3 文件或目录权限相关的命令

——5.3.1 chown

chown --- 修改文件或目录属主或属组信息
单独修改属主: chown oldboy 目录或文件信息
单读修改属组: chown .oldboy 目录或文件信息
全部继续修改: chown oldboy.oldboy 目录或文件信息

-R --- 递归修改目录的数据属主和属组信息
chown oldboy oldboy.txt #只修改了文件的所有者 
chown oldboy.oldboy oldboy.txt #修改了文件的所有者和属于的组 

——5.3.1 chmod

chmod --- 修改文件的12位权限位 另外3位是什么???
精确修改: 对某个用户的权限进行精确修改
批量修改: 对所有用户的权限进行批量修改

-R --- 递归修改目录的数据12位权限位

—5.4 文件或目录默认权限的设置方法

——5.4.1 说明

问题一: root用户创建的文件为什么默认值位644
普通用户创建的文件为什么默认值位664
问题二: root用户创建的目录为什么默认值位755
普通用户创建的目录为什么默认值位775

预备知识:
umask: 是一个数值, 利用这个数值进行运算 生成默认文件或目录权限数值
umask xxx 临时修改

root用户默认umask=0022
文件: 666
偶数:创建文件默认权限值=666-umask=666-22=644
奇数:创建文件默认权限值=666-umask=666-33=633 减去奇数的位结果上要再加1
644
目录: 777
偶数: 创建目录默认权限值=777-umask=777-22=755
奇数: 创建目录默认权限值=777-umask=777-33=744

普通用户默认umask=0002
文件: 666
偶数: 创建文件默认权限值=666-umask=666-2=664
奇数: 创建文件默认权限值=666-umask=666-3=663 奇数运算位再加1 664

目录: 777
偶数: 创建目录默认权限值=777-umask=777-2=775
奇数: 创建目录默认权限值=777-umask=777-3=774

需求: 批量创建100个文件, 让100个文件默认权限为600
方法1:touch {01..100}.txt
      chmod 600 {01..100}.txt
方法2:(umask 066;touch /oldboy/oldboy{11..20}.txt)

——5.4.2 默认的umask设置

[root@linux ~]# cat /etc/profile
if [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; then
umask 002
else
umask 022
fi
以上表示位一段shell脚本信息, 变量 脚本判断语句
-----------------------------
条件01[ $UID -gt 199 ]: UID环境变量 记录当前登陆用户id数值信息
   是否当前用户uid数值大于199
   大于 > greater than --> -gt
   小于 < less than --> -lt
   等于 = equal --> -eq
   大于等于 >= Greater than equal --> -ge
   小于等于 <= less than equal --> -le
   不等于 <> != no equal --> -ne
条件02[ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]: 用户名和用户组名是否相同
   /usr/bin/id -gn --- 显示当前用户组名
   /usr/bin/id -un --- 显示当前用户名

——5.4.3 shell的3循环语句

for循环: 有限循环.不会一直循环
for num in {01..10000000000000000}
do
循环做什么事
done
-----------------------------------------
while循环: 死循环, 条件如果为真, 一直循环; 条件不满足, 结束循环
while [ 条件满足 ]
do
循环做什么事
done
---------------------------------------
until循环: 死循环, 条件如果为真, 结束循环; 条件不满足, 一直循环
until [ 条件不满足 ]
do
循环做什么事
done

——5.4.3 shell的3判断语句

if判断语句: 单分支语句 条件满足时,就做什么事
if 男生身价1个亿
then
优秀女生和你在一起
fi
-------------------------------
if判断语句: 双分支语句 条件满足时,做什么事,条件不满足,又做什么事
if 男生身价1个亿
then
优秀女生和你结婚
else
和你谈恋爱
fi
----------------------------
if判断语句: 多分支语句 首选条件满足,做什么,其次条件满足,做什么,再次条件满足,做什么,全部条件都不满足,做什么
if 男生身价1个亿
then
优秀女生和你结婚
elif 男生身价1000万
then
和你交朋友
elif 男生身价100万
then
见个面
else
你是个好人
fi

6. sudo详细配置

—6.1 sudo说明

普通用户进行操作:
sudo -l --- 显示普通用户拥有了root用户哪些能力
sudo -k --- 清除当前用户的密码缓存

应用sudo能力时,需要在命令前面加上sudo
[oldboy002@linux ~]$sudo cat /etc/shadow

—6.2 visudo配置方法

root用户进行sudo授权:
visudo -- vi sudo配置文件==vim /etc/sudoers
第一步: 切换到92gg(centos7) 98gg(centos6)

第二步: 开始编辑进行授权某一个或几个命令
[root@linux ~]# visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
#给谁进行授权 根据主机进行授权 授予什么权力
oldboy002 ALL=(ALL) /usr/bin/cat /etc/shadow
oldboy002 ALL=(ALL) /usr/bin/cat /etc/shadow, /bin/rm -rf /oldboy/etc/ 
说明: 
a.对于一些危险命令进行授权时,需要加上参数 
b.对于有些命令, root用户命令绝对路径 和 普通用户命令绝对路径不统一 
c.授权大量命令信息,并排除指定命令  
  oldboy002 ALL=(ALL) ALL
  oldboy002 ALL=(ALL) /usr/bin/*,/bin/*,! /bin/rm -rf /root/,! /bin/vi 
d.取消sudo操作时密码认证 
  oldboy002 ALL=(ALL) NOPASSWD: /usr/bin/*, /bin/*, !/bin/rm -rf /root/

第三步: 保存退出sudo文件时,会有错误提示
       因为命令信息必须写成绝对路径
[oldboy002@linux ~]$ sudo -l
User oldboy002 may run the following commands on linux:
(ALL) /usr/bin/cat /etc/shadow

—6.3 visudo命令特点

1) 可以直接编辑sudo配置
2) 具有文件语法检查功能
[root@linux ~]# visudo -c --- 手动对/etc/sudoers语法检查
>>> /etc/sudoers: syntax error near line 94 <<<
parse error in /etc/sudoers near line 94

—6.4 chattr文件加锁

[root@linux ~]# chattr +i /etc/shadow  加锁
[root@linux ~]# lsattr /etc/shadow  查看
[root@linux ~]# chattr -i /etc/shadow 解锁
[root@linux ~]# mv chattr /usr/oldboy_ch 把命令移动到自己创建的安全文件夹

7. 系统中特殊权限位

9个权限,特殊的三个权限位:
系统中的权限位总共位 12位

—7.1 setuid属主权限位

setuid: 在属主权限位生成一个s位(4)
总结: 让一些文件执行的时候, 可以让普通用户可以以root属主的身份进行执行
设置setuid方法:
方法一: 利用字符进行特殊权限位设置
[root@linux ~]# ll /usr/bin/cat
-rwxr-xr-x. 1 root root 54080 Apr 11 2018 /usr/bin/cat
[root@linux ~]# chmod u+s /usr/bin/cat
[root@linux ~]# ll /usr/bin/cat
-rwsr-xr-x. 1 root root 54080 Apr 11 2018 /usr/bin/cat
[root@linux ~]# chmod u-s /usr/bin/cat
[root@linux ~]# chmod u-s /usr/bin/cat
[root@linux ~]# ll /usr/bin/cat
-rwxr-xr-x. 1 root root 54160 Oct 31 2018 /usr/bin/cat
方法二: 利用数值进行特殊权限位设置
[root@linux ~]# ll /usr/bin/vi
-rwxr-xr-x. 1 root root 910072 Apr 11 2018 /usr/bin/vi
[root@linux ~]# chmod 4755 /usr/bin/vi
[root@linux ~]# ll /usr/bin/vi
-rwsr-xr-x. 1 root root 910072 Apr 11 2018 /usr/bin/vi
[root@linux ~]# ll /bin/vim
-rwxr-xr-x. 1 root root 2294208 Oct 31 03:57 /bin/vim
[root@linux ~]# chmod 4755 /bin/vim
[root@linux ~]# ll /bin/vim
-rwsr-xr-x. 1 root root 2294208 Oct 31 03:57 /bin/vim

—7.2 setgid属组权限位

setgid: 在属组权限位生成一个s位(2)
总结: 让一些文件执行的时候, 可以让普通用户可以以root属组身份进行执行
方法一: 利用字符进行特殊权限位设置
[root@linux ~]# ll /bin/vim
-rwsr-xr-x. 1 root root 2294208 Oct 31 03:57 /bin/vim
[root@linux ~]# chmod g+s /bin/vim
[root@linux ~]# ll /bin/vim
-rwsr-sr-x. 1 root root 2294208 Oct 31 03:57 /bin/vim

方法二: 利用数值进行特殊权限位设置
[root@linux ~]# ll /bin/cat
-rwsr-xr-x. 1 root root 54080 Apr 11 2018 /bin/cat
[root@linux ~]# chmod 2755 /bin/cat
[root@linux ~]# ll /bin/cat
-rwxr-sr-x. 1 root root 54080 Apr 11 2018 /bin/cat

—7.3 Stickybit其它用户权限位

Stickybit: 其他用户权限位生成一个t位(1)
对一个目录设置粘滞位权限, 目录中数据只能被自己进行删除, 其他用户没有权力删除
如何设置粘滞位:
方法一: 利用字符进行特殊权限位设置
[root@linux ~]# ll /share_dir/ -d
drwxr-xrwx 2 root root 26 Jan 25 18:38 /share_dir/
[root@linux ~]# chmod o+t /share_dir/
[root@linux ~]# ll /share_dir/ -d
drwxr-xrwt 2 root root 26 Jan 25 18:38 /share_dir/

方法二: 利用数值进行特殊权限位设置
[root@linux ~]# ll /share_dir/ -d
drwxr-xrwx 2 root root 26 Jan 25 18:43 /share_dir/
[root@linux ~]# chmod 1777 /share_dir/
[root@linux ~]# ll /share_dir/ -d
drwxrwxrwt 2 root root 26 Jan 25 18:43 /share_dir/

特殊说明: 系统中已经设置了一个共享目录
[root@linux ~]# ll -d /tmp/
drwxrwxrwt. 10 root root 248 Jan 25 18:46 /tmp/
PS: 一定不能随意修改此目录的权限 (mysql服务需要依赖/tmp目录权限为1777)

8. 用户操作行为审计

看某用户的操作记录:
[root@linux ~]# cat /home/oldboy002/.bash_history 
跳板机/堡垒机:
jumpserver
crazyEYE
shell脚本
齐治堡垒机
赞(1)

评论 抢沙发

评论前必须登录!

 

LNMP社群 不仅仅是技术

关于我们网站地图