1. OpenGL 渲染管线
OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)和投影变换(Projection Transformation)。做个比喻,计算机图形开发就像我们照相一样,目的就是把真实的场景在一张照相纸上表现出来。那么观测变换的过程就像是我们摆设相机的位置,选择好要照的物体,摆好物体的造型。而投影变换就像相机把真实的三维场景显示在相纸上一样。下面就分别详细的讲一下这两个过程。
1.1模型观测变换
让我们先来弄清楚OpenGL中的渲染管线。管线是一个抽象的概念,之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像水从一根管子的一端流到另一端,这个顺序是不能打破的。先来看看下面的图1:
图1 OPENGL渲染管线
图中显示了OpenGL图形管线的主要部分,也是我们在进行图形编程的时候常常要用到的部分。一个顶点数据从图的左上角(MC)进入管线,最后从图的右下角(DC)输出。MC是Model Coordinate的简写,表示模型坐标。DC是Device Coordinate的简写,表示设备坐标。 当然DC有很多了,什么显示器,打印机等等。这里DC我们就理解成常说的屏幕坐标好了。MC当然就是3D坐标了(注意:我说的3D坐标,而不是世界坐 标),这个3D坐标就是模型坐标,也说成本地坐标(相对于世界坐标)。MC要经过模型变换(Modeling Transformation)才变换到世界坐标,图2:
图2 世界坐标系和模型坐标系
变换到世界坐标WC(World Coordinate)说简单点就是如何用世界坐标系来表示本地坐标系中的坐标。为了讲得更清楚一些,这里举个2D的例子。如图3:
图3 世界坐标系和模型坐标系的计算
图中红色坐标系是世界坐标系WC,绿色的是模型坐标系MC。现在有一个顶点,在模型坐标系中的坐标为(1,1),现在要把这个模型坐标转换到世界坐标中来表示。从图中可以看出,点(1,1)在世界坐标系中的坐标为(3,4),现在我们来通过计算得到我们希望的结果。首先我们要把模型坐标系MC在世界坐标系中表示出来,使用齐次坐标(Homogeneous Coordinate )可以表示为矩阵(注意,本教程中使用的矩阵都是以列向量组成):
其中,矩阵的第一列为MC中x轴在WC中的向量表示,第二列为MC中y轴WC中的向量表示,第三列为MC中的原点在WC中的坐标。对齐次坐标系不了解的同学,请先学习游戏数学方面的知识。有了这个模型变换矩阵后,用这个矩阵乘以在MC中表示的坐标就可以得到该坐标在世界坐标系中的坐标。所以该矩阵和MC中的坐标(1,1)相乘有:
这也正是我们需要的结果。现在让我们把相机坐标也加进去,相机坐标也称为观测坐标(View Coordinate),如图4和图5。
图4 ModelView变换的三个坐标系
图5 ModelView变换计算
来看看MC坐标中的点(1,1)如何在相机坐标中表示。从图5中可以直接看出MC中的点(1,1)在相机坐标系VC中为(-2,-2)。和上面同样的道理,我们可以写出相机坐标系VC在世界标系WC中可以表示为:
那么世界坐标系中的点转换为相机坐标系中的点我们就需求VC的逆矩阵:
那么世界坐标系WC中的点(3,4)在相机坐标系VC中坐标为:
上面的变换过程,就是可以把模型坐标变换为相机坐标。在OpenGL中,当我们申明顶点的时候,有时候说的是世界坐标,这是因为初始化的时候世界坐标系、模型坐标系和相机坐标系是一样的,重合在一起的。所以OpenGL中提供了模型观测变换,它是把模型坐标系直接转换为相机坐标系,如图4。现在我们已经计算得到了VC-1和MC,如果把VC-1和 MC相乘,就可以得到模型坐标在相机坐标中的表示。为了得到模型坐标系中的坐标在相机坐标系中的表示,这就是OpenGL中的ModelView变换矩 阵。这也是ModelView变换的名字的由来,它是通过了上面两个步骤得到的。那么这里,ModelView变换矩阵M为:
现在只要用上面的模型观测矩阵M乘以模型坐标系MC中的坐标就可以得到相机坐标系中的坐标了。模型观测变换的关键就是要得到相机坐标系中的坐标,因为光照等计算都是在这个这个坐标系中完成的。下面我们实际OpenGL程序中检查一下。在程序中,为了计算方便,我们使用图6中的模型。
图6 ModelView变换计算模型
根据图中的数据,我们分别可以写出对应MC和VC-1,从而求得观测变换矩阵M。
现在程序中用glGetFloatv()这个函数来获得当前矩阵数据来检查一下。
- float m[16] = {0}; //用来保存当前矩阵数据
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glGetFloatv(GL_MODELVIEW_MATRIX, m);
- //相机设置,View 变换
- gluLookAt(0.0, 0.0, 5.0,
- 0.0, 0.0, 0.0,
- 0.0, 1.0, 0.0);
- glGetFloatv(GL_MODELVIEW_MATRIX, m);
- //投影设置
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(-10,10,-10,10,-10,10);
- glMatrixMode(GL_MODELVIEW);
- //Modeling变换
- glTranslatef(0, 0, -3);
- glGetFloatv(GL_MODELVIEW_MATRIX, m);
- glBegin(GL_POINTS);
- glVertex3f(1,1,0);
- glEnd();
如果在上面程序段中最后一个glGetFloatv(GL_MODELVIEW_MATRIX, m)处设定断点的话,就可以看到图7所显示的数据。
图7 ModelView变换矩阵数据
到这里,整个ModelView变换就完成了。通过ModelView变换后得到是相机坐标系内的坐标。在这个坐标系内典型的计算就是法线了。现在再来看看后面一个阶段。
1.2投影变换
先还是复习一下OpenGL的渲染管线。图1中可以看到,在投影变换(Projection Transformation)中也分为两个部分,第一个部分是将上个阶段得到的坐标转换为平面坐标,第二个部分是将转换后的平面坐标在进行归一化并进行剪裁。一般地,将三维坐标转换为平面坐标有两种投影方式:正交投影(Orthogonal Projection)和透视投影(Perspective Projection)。
1.2.1 正交投影
正交投影很简单,如图8,对于三维空间中的坐标点和一个二维平面,要在对应的平面上投影,只需将非该平面上的点的坐标分量改为该平面上的坐标值,其余坐标不变。
图8 正交投影
比如将点(1,1,5)正交投影到z=0的平面上,那么投影后的坐标为(1,1,0)。在openGL中,设置正交投影可以使用函数:
- glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)
该函数可以设置正交投影的投影空间,在该空间以外的坐标点就不会被投影到投影平面上。函数中的六个参数分是投影空间六个平面,如图9:
图9 OpenGL正交投影空间和投影变换
在图9中,大的投影空间是根据这六个参数设置的投影空间,OpenGL会自动将该空间归一化,也就是将该空间或立方体转化为变长为1的正六面体投影 空间,并且该证六面体的中心在相机坐标系的原点。一旦设置使用glortho函数设置投影空间,OpenGL会生成投影矩阵。这个矩阵的作用就是将坐标进 行正交投影并且将投影后的坐标正规化(转换到-1到1之间)。要注意的是,生成该矩阵的时候,OpenGL会把右手坐标系转换为左手坐标系。原因很简单, 右手坐标系的Z轴向平面外的,这样不符合我们的习惯。该矩阵的矩阵推导这里就不详细说明了,不了解的同学可以参考游戏数学方面资料,这里只给出正交投影矩 阵。
这个矩阵看来很复杂,其实计算很简单。举个例子,现在设置了这样的正交投影空间glOrtho(-10,10,-10,10,-10,10),这是个正六面体空间,变长为10。把这些参数带入上面的矩阵可以得到
现在还是在OpenGL程序中来检查一下。在OpenGL程序中添加下面代码段:
- //投影设置
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(-10,10,-10,10,-10,10);
- glMatrixMode(GL_MODELVIEW);
- glGetFloatv(GL_PROJECTION_MATRIX,m)
在glGetFloatv(GL_PROJECTION_MATRIX,m)处设定断点就可以看到图10中所显示的信息。
图10 正交变换矩阵数据
1.2.2透视投影
透视投影和正交投影最大的区别就是透视投影具有远近感。
图11 透视投影
透视投影采用了图11中的模型,这样的模型就是保证远的物体看起来小,近的物体看起来大。 在OpenGL中设置透视投影可以使用函数:
- void APIENTRY gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);
该函数也会根据给定的参数生成一个投影空间。如图11中,该投影空间是一个截头体。同样地,OpenGL会自动生成透视投影矩阵,该矩阵也会让3D坐标投影在投影平面上,并且将投影后的坐标也进行正规化。下面也直接给出OpenGL中使用的透视投影矩阵。
下面在OpenGL中添加下面代码段:
- //投影设置
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- gluPerspective(45, 1.0, 1.0, 100);
- glMatrixMode(GL_MODELVIEW);
- glGetFloatv(GL_PROJECTION_MATRIX,m)
设置断点后,我们可以看到图12中显示的数据。
图12 透视变换矩阵数据
到此为止,整个投影变换就完成了。透过投影变换后得到的是正规化的投影平面坐标。这为下一个阶段的视口变换(View port Transformation)做好了准备。
1.3视口变换
现在到了最后一个阶段了。这个阶段叫做视口变换,它把上个阶段得到的正规化的投影坐标转化为windows 窗口坐标。视口变换会将投影平面上的画面映射到窗口上。在OpenGL中可以使用函数
- GLAPI void GLAPIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);
来进行对窗口的映射,如图13。
图13 视口变换glViewport(width/2, 0, width/2, height/2)
举个例子说明,比如上个阶段中得到了一个顶点的坐标为(0,0,0.5,1),根据这个坐标,该顶点位于投影平面的正中间。如果将该点映射到大小为 50*50的窗口上时,那么它应该位于屏幕的中间,坐标为(25,25, 0.5,1)。当然这里深度值0.5是不会改变的。有的同学肯定有疑问了,既然投影到了窗口上,那么还要深度值0.5干什么?这里要注意的是,虽然在窗口 上显示时只需要x,y坐标就够了,但是要在2D窗口上显示3D图形时深度值是不可少的。这里的深度值不是用于显示,而是用于在光栅化的时候进行深度测试。
OpenGL也会根据glViewport函数提供的参数值生成一个视口变换矩阵
该矩阵把上个阶段得到的正规化坐标映射到窗口上,并且将正规化坐标中的深度值在转换到0到1之间。所以在深度缓冲中最大值为1,最小值为0。视口变换结束 后,OpenGL中主要的图形管线阶段就算完成了,后面就是光栅化等等操作。再来回顾一下图1,现在相信大家对这个渲染管线有了一定的认识了,也明白了每 一个阶段对应的变换矩阵以及如何进行坐标之间的转换的。
2. 屏幕坐标转换为世界坐标
通过前面的教程,以及现在大家对OpenGL整个渲染管线理解后,现在要将屏幕上一点坐标转换为世界坐标就比较容易了。从图形管线的开始到结束,一 个模型坐标系中的坐标被转化为了屏幕坐标,那么现在把整个过程倒过来的话,屏幕上一点坐标也可以转为为世界坐标。只要在对应的阶段求得对应变换矩阵的逆矩 阵,就可以得到前一个阶段的坐标。这整个过程可以用图14表示。
图14屏幕坐标转换为世界坐标
图中显示的过程完全就是OpenGL渲染管线的逆过程,通过这个过程,屏幕上的点就可以转化为世界坐标系中的点了。可能又有的同学要问,当鼠标点击屏幕上一点的时候并没有深度信息,转换的时候要怎么办呢?这个时候可以使用OpenGL函数
- void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
该函数能够获得屏幕上一点对应像素的深度信息。有了这个深度信息,就可以利用上面过程把屏幕上一点转换为世界坐标了。在OpenGL中,上面的过程其实已经有现成的函数可以使用,那就是
- int APIENTRY gluUnProject (
- GLdouble winx, GLdouble winy,
- GLdouble winz,
- const GLdouble modelMatrix[16],
- const GLdouble projMatrix[16],
- const GLint viewport[4],
- GLdouble *objx, GLdouble *objy,
- GLdouble *objz);
该函数直接将屏幕上一点转换对应的世界坐标,该函数的内部实现其实还是上面的那么逆过程。下面给出利用该函数获取世界坐标的代码段。
- GVector screen2world(int x, int y)
- {
- GLint viewport[4];
- GLdouble modelview[16];
- GLdouble projection[16];
- GLfloat winX, winY, winZ;
- GLdouble posX, posY, posZ;
- glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
- glGetDoublev(GL_PROJECTION_MATRIX, projection);
- glGetIntegerv(GL_VIEWPORT, viewport);
- winX = (float)x;
- winY = (float)viewport[3] - (float)y;
- glReadPixels(x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);
- gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);
- GVector v(4, posX, posY, posZ, 1.0);
- return v;
- }
代码中函数返回类型GVector是用户定义的向量类,返回的是齐次坐标。
Linux下对于新添加的用户,用户密码过期时间是从/etc/login.defs中PASS_MAX_DAYS提取的,普通系统默认就是99999,而有些安全操作系统是90。更改此处,只是让新建的用户默认密码过期时间变化,已有用户密码过期时间仍然不变。
[root@linuxidc ~]# chage --help
Usage: chage [options] user
Options:
-d, --lastday LAST_DAY set last password change to LAST_DAY
-E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE
-h, --help display this help message and exit
-I, --inactive INACTIVE set password inactive after expiration
to INACTIVE
-l, --list show account aging information
-m, --mindays MIN_DAYS set minimum number of days before password
change to MIN_DAYS
-M, --maxdays MAX_DAYS set maximim number of days before password
change to MAX_DAYS
-W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS
chage:密码失效是通过此命令来管理的。
参数意思:
-m 密码可更改的最小天数。为零时代表任何时候都可以更改密码。
-M 密码保持有效的最大天数。www.linuxidc.com
-W 用户密码到期前,提前收到警告信息的天数。
-E 帐号到期的日期。过了这天,此帐号将不可用。
-d 上一次更改的日期
-i 停滞时期。如果一个密码已过期这些天,那么此帐号将不可用。
-l 例出当前的设置。由非特权用户来确定他们的密码或帐号何时过期。
[root@linuxidc ~]# chage -l root
Last password change : Oct 19, 2010
Password expires : never
Password inactive : never
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 99999
Number of days of warning before password expires : 7
更改用: chage -M 90 root
[root@linuxidc ~]#chage -M 90 root
[root@linuxidc ~]#chage -l root
如果以后添加一个用户,那么默认的时间还是没改的,还必须得去/etc/login.defs修改PASS_MAX_DAYS 的默认值.那么如果我直接修改全局/etc/login.defs所在的用户会跟着改变吗? 据我的测试是不会改变的,除非重启后,但我们的服务器不是你想重启的就可以重启的!如果管理严格的地方,重启还得经过很多程序步骤.改完全局时,没有更改 的用户,想要让他也同样具备此功能.就得一个个的执行!
你也可以直接用vim 编辑器去编辑PASS_MAX_DAYS 99999
也可以用其它的工具
[root@linuxidc ~]#sed -i.bak -e 's/^\(PASS_MAX_DAYS\).*/\1 90/' /etc/login.defs
查看一下是否
[root@linuxidc ~]#cat /etc/login.defs |grep "PASS_M";
强制用户登陆时修改口令
[root@linuxidc ~]#chage -d 0 username(linux)
[root@linuxidc ~]#passwd -f username(solaris)
强制用户下次登陆时修改密码,并且设置密码最低有效期0和最高有限期90,提前15天发警报提示
[root@linuxidc ~]#chage -d 0 -m 0 -M 90 -W 15 root(linux)
[root@linuxidc ~]#passwd -f -n 0 -x 90 -w 15 root(solaris)
查看某个用户的密码设置情况
[root@linuxidc ~]#chage -l username
修改密码配置文件
[root@linuxidc ~]#vi /etc/login.defs
Linux ssh建立一个新的用户包括两个步骤,第一步是使用useradd命令完成一个新用户的初始化设置工作;第二步是用passwd为这个新用户设置密码。例如,我们要给系统添加一个用户叫floatboat ,密码为lan2010,那相关的操作是:
useradd floatboat -d /u01/Lanlp<回车>
这时候系统没有任何显示。接着:
passwd floatboat <回车>
系统显示:
Changing password for user floatboat
New UNIX password:
你输入:
lan2010<回车>
注意,由于linux并不采用类似windows的密码回显(显示为*号)——为避免你输入密码时被人注意到有多少位——所以,输入的这些字符你是看不见的。
系统显示:
Retype new UNIX password:
你再重新输入一次密码,然后回车确认,这时系统会显示:
passwd:all authentication tokens updated successfully
表示你修改密码成功了。
到这里,新用户的创建工作就算完成了。
下面,我们再补充一些有关增加新用户的常识:
1、useradd所做的初始化操作已经包括在/home目录下为floatboat帐号建立一个名为floatboat的主目录。如果你不想 使用这个缺省的目录,而希望把他的主目录放在/home/goal里(还放在/home下,只是一种良好的习惯,没有其他什么特别的要求),可以使用 useradd的参数-d,命令如下:
useradd -d /home/goal floatboat
2、useradd的初始化操作还包括为用户单独建立一个与用户名同名的组(floatboat组)。这叫用户私有组的机制,与默认组机制相对 应。对用户分组一是方便管理,二是可以明确权限。复杂的我们将在以后的深入内容中探讨。我们如果想让此用户加入一个已有的组的话,可以使用-g参数。例如 我们想让floatboat加入webusers组,那么可以使用以下命令:
useradd -g webusers floatboat
同样的,我们还可以使用-G参数使他同时加入多个组,例如webusers和ftpusers:
useradd -G ftpusers,webusers floatboat
3、passwd命令为一个用户设置密码,但它实质上是一个修改密码的程序。只有超级用户和用户自己可以修改密码,其它的普通用户没有给他修改 密码的 权利。用户密码的组成要尽量的复杂,最好包括字母、数字和特殊符号,而且最好设成6位以上。太短passwd程序不允许,只是单纯的字母或单纯的数 字,passwd也会有意见。你都会看见passwd出现的提示的,不要害怕,仔细看看到底它是怎么说的:)
4、你在增加一个新用户的时候,也可以设置用户登录的shell。缺省的,系统提供了/bin/bash。你如果非要指定的话,可以使用-s参数就可以了。例如
useradd -d /www -s /usr/bin/passwd floatboat
注意,这些参数是可以一块使用的,如上例所示,它表示增加新用户,并把其主目录路径设置在/www,登录的shell为/usr/bin/passwd。关于shell的更详细的说明,请参考下面的修改用户的个人设置相关内容。
5、删除一个用户可以使用userdel命令,直接带用户名做参数就可以了。
修改用户的设置
对现有用户的修改,比较常用的主要是修改密码(使用passwd就好了),修改用户的登录shell,修改用户所属的默认组,设置帐号有效期,修改用户的说明信息等等,偶尔也会用到修改用户主目录。
修改用户的登录shell
使用chsh命令可以修改自己的shell,只有超级用户才能用chsh username为其它用户修改shell设置。注意,指定的shell必须是列入/etc/shells文件中的shell,否则该用户将不能登陆。
一般,比较常见的shells文件包括下面这些shell:
/bin/bash2
/bin/bash
/bin/sh
/bin/ash
/bin/bsh
/bin/tcsh
/bin/csh
而网管们还喜欢在里面加上/usr/bin/passwd,这是为了不然用户通过控制台或telnet登录系统,却可以使用修改帐户密码(比如在FTP里 用)。以及/bin/false,也就是不让这个用户登录的意思喽^&^,连FTP也不能用。
你也可以使用usermod命令修改shell信息,如下所示:
usermod -s /bin/bash floatboat
其中/bin/bash和floatboat应取相应的shell路径文件名及用户名。
还有一种情况,就是你为用户设置了一个空的shell(就是""),也就是说,这个用户没有shell。呵呵,绝对没有在我还未曾见过,因为这种用户登录后,系统还是会给它一个shell用的。不信你试试:
usermod -s "" floatboat
这种用户根据系统的不同,会有一个sh或bash进行操作,我也没有看出功能上和其它普通用户登录有什么不同。
修改用户所属的默认组
这个功能也可以通过usermod命令来实现,使用-g参数,例如把floatboat的默认组改为nobody,可以使用如下命令:
usermod -g nobody floatboat
nobody在类UNIX系统中一般都意味着没有任何权限。
设置帐号有效期
如果使用了影子口令,则可以使用如下命令来修改一个帐号的有效期:
usermod -e MM/DD/YY username
例如把用户floatboat的有效期定为2001年12月31日:
usermod -e 12/31/01 floatboat
如果把该用户的有效期设为已经过去的时间,就可以暂时禁止该用户登录系统。
修改用户的说明信息
修改用户的说明信息,最简单的方法莫过于直接修改/etc/passwd文件,找到对应的用户记录行,例如下列行:
floatboat:x:503:503::/home/floatboat:/bin/bash
你可以直接在第四个冒号和第五个冒号之间插入该用户的说明就可以了。其实,很多用户设置都可以在这修改,比如该行最后一部分/bin/bash就是用户登录shell的设置。关于这个/etc/passwd文件,我们后面将进一步的深入探讨。
修改用户主目录
修改用户的主目录主要使用usermod命令的-d参数,例如:
usermod -d /www floatboat
这一行将floatboat的主目录改到/www。如果想将现有主目录的主要内容转移到新的目录,应该使用-m开关,如下所示:
usermod -d -m /www floatboat
文件目录的权限
linux下,每一个文件、每一个目录都有一个属主,并针对用户自己、用户所在组、其它所有帐号(组)分别设定读、写、执行三种权限。例如,我(假定是webusers组的floatboat帐户的拥有者)使用如下命令建立一个新的文件
touch mytestfile
然后我们使用ls -l mytestfile这一命令来查看这个文件的权限状态(关于ls命令,可以查阅本站的命令查询),可以得到如下的屏幕输出显示:
-rw-rw-r-- 1 floatboat webusers 0 Feb 6 21:37 mytestfile
输出由空格分为9个部分,我们比较关心第一、三、四个字段,分别表示文件权限属性、文件所有者帐户、文件所属组。
◆使用chown命令修改文件的主人
当你新建立一个文件的时候,文件的所有者当然就是你了。这一事实只有超级用户(比如说root)才可以通过chown命令改变(例如chown otheruser mytestfile,把mytestfile文件的属主改为otheruser)。普通用户不能把自己的文件“送”给别人,不然你把有特殊目的的程序给 了root怎么办?:)
chown命令的用法比较简单。这里我先假设你现在拥有超级用户权限,那么你就可以使用如下命令将一个文件“送给”floatboat了:
chown floatboat /home/floatboat/thefileisrootcreate.txt(假定该文件是由root创建的)
修改一个目录的所有者也是类似的:
chown floatboat /home/newboat
当然,如果这个目录还有子目录及文件需要同时送给floatboat,chown也是支持-R参数的:
chown -R floatboat /home/newboat
如果你同时想修改文件/目录所属的组的话,你可以使用以下命令方便的达到目的:
chown -R floatboat.ftpusers /home/newboat
这样,不但文件主人得到了修改,文件所属的组也变成了ftpusers
◆修改文件的组属性
文件所属组你倒是可以改变,前提是:
1、你的超级用户。
2、你同时属于两个或两个以上的组。
两个条件你至少具备一个,你才能够把文件所属旧组变为新组。使用如下的命令将当前目录下所有html文件所属的组改为httpd:
chgrp httpd *.html
和chown命令一样,chgrp也可以使用-R参数对一个目录内的所有文件和子目录进行递归的修改组属性。
<提示>:你可以使用不带参数的groups命令查看自己属于哪个组。
文件权限的设定是我们这一小节讨论的核心,我们主要介绍chmod命令的两种用法。
◆使用访问字符串设置文件目录权限 正如前面所说的,每一个文件、目录都针对用户自己、用户所在组、其它所有帐号(组)分别有读、写、执行三种权限及其组合。当一个普通用户新建一 个文件 的时候,它默认的访问权限显示就如我们刚才所举例子的第一个字段所示。总共十位字符“-rw-rw-r--”,第一位是目录区分标志,如果是d的话,表示 这是一个目录。第二到四位分别表示文件所有者的读(r:read)、写(w:write)、执行(x:execute)属性,第五到七位是文件所属组的 读、写、执行权限,第八到第十位则是其它用户的读、写、执行权限。如果对应的位是相应的字母,就是有这相应权限,否则为“-”,表示没有获得这个许可。象 刚才例子中的文件就是自己可读写,本组可读写,其它用户可读,所有的用户(包括自己)都不能执行它。
我们的用u、g、o分别来指代用户(user)、组(group)、其它帐户(other),就可以方便的设置文件和目录的权限了。当然,我们也可以用a来表示所有的这三项。
例如,我们要对所有perl的脚本文件设定权限,对所有用户都可以读和执行,文件所有者还允许写许可,那么我们可以使用如下命令:
chmod a+rx,u+w *.pl
注意:如果要使用多个访问字符串,它们之间要用逗号隔开,各个许可字符串之间不允许有空格。正如上例所示。
如果要修改目录中所有文件和子目录的权限属性,可以使用chmod提供的-R参数来递归修改。例如,下列命令将/www/site1目录及其下面的子目录的权限属性设定为所有者和组可读、写、执行,其它用户不可访问:
chmod -R a+rwx,o-rwd /www/site1
注意,不要轻易使用-R选项,这可能会带来安全隐患。
使用字符串方便了理解,单输入那么多字母还是有点累,如果你对8进制有些概念的话,可以使用下面介绍的方法来做权限设置。
◆使用八进制数设置文件目录权限
我们知道,在ls -l的输出中,文件权限表示为“-rw-rw-r--”,前一位只和是否为目录有关,其它九位正好可以分成三段,每段三位,“rw-”、“rw-”和 “r--”,“-”代表无效“0”,其它字符代表有效“1”,那么这个文件的权限就是“110”、“110”、“100”,把这个2进制串转换成对应的8 进制数就是6、6、4,也就是说该文件的权限为664(三位八进制数)。我们也可以使用类似这种三位八进制数来设定文件授权,如上边两个例子,就也可以写 为:
chmod 755 *.pl
chmod -R 770 /www/site1
是不是很简洁?关键在于你能根据你需要设定的权限正确的选择八进制数(利用八进制数的二进制表示可以非常轻易的做到这一点)。
◆读、写、执行的权限说明
1、所谓写的权限,也就是对文件修改和删除的权限。如果目录的写权限也对你开放了,则可以创建、删除或修改该目录下的任何文件或自目录——即使该文件和子目录并不属于你。
2、对目录有只读许可的用户,不能用cd命令进入该目录;还必须同时有执行许可才可以进入该目录。
3、必须同时拥有读和执行权限才可以使用ls这样的程序列出目录内容清单。
4、只对目录有执行权限的用户,想访问该目录下的文件有读权限的文件,必须知道该文件名才可以访问。
两个重要文件:passwd与group
在linux的安全机制里,/etc/passwd与/etc/group这两个文件占着非常重要的地位。它们控制着linux的用户和组一些重要设置。
◆/etc/passwd文件说明
下面是一个RHlinux里普通的passwd文件的例子:
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:
daemon:x:2:2:daemon:/sbin:
……
operator:x:11:0perator:/root:
games:x:12:100:games:/usr/games:
gopher:x:13:30:gopher:/usr/lib/gopher-data:
ftp:x:14:50:FTP User:/home/ftp:
nobody:x:99:99:Nobody:/:
xfs:x:43:43:X Font Server:/etc/X11/fs:/bin/false
named:x:25:25:Named:/var/named:/bin/false
postgres:x:26:26ostgreSQL Server:/var/lib/pgsql:/bin/bash
lanf:x:500:500::/home/hujm:/bin/bash
mysql:x:101:101:MySQL server:/var/lib/mysql:/bin/bash
imnotroot:x:0:0::/home/imnotroot:/bin/bash
在这个文件里只有一个普通帐号lanf。其它都是系统或系统服务的进程需要的帐号,包括我们非常熟悉的root这个超级用户。在passwd的文件里,每一行被冒号(":")分成7个部分,分别是:
[用户名]:[密码]:[UID]:[GID]:[身份描述]:[主目录]:[登录shell]
其中:
⒈[用户名]是passwd文件里各记录行唯一的有"唯一性"要求的域。也就是说每一行的第一个区域的内容都不能相同,其它区域就无所谓了。
⒉[密码]区域在以前,保存着一个经过不可逆的哈希算法进行DES加密的13位字符,但不包括单引号和冒号。这13位字符中,前两位是密钥,在 加密的 时候随机生成的。由于这个字符串不包括单引号,所以以前有一种不修改密码又禁止用户登录的方式就是在密码前面加一个单引号。值得注意的是,现在由于使用了 shadow口令,在密码区域只有一个x字符。
⒊[UID]虽然是系统用来标志文件归属,确定各种权限的标志,但这个区域的内容并不要求唯一的。比较常见而又与安全问题相关的一个例子是有多 个 UID和GID均为0的用户帐号。注意到在该文件最后一行还有一个UID和GID为0的用户imnotroot,虽然它声称自己不是root,但是它却有 和root完全相同的权限,因为系统并非根据[用户名],而是根据UID和GID来分用户的权力的。所以,这种情况无疑为系统埋下了安全的zhadan。 但是,当 imnorroot做锁定屏幕等操作的时候,如果它的密码和root的不一样,它将无法解锁,因为系统只是查到第一个UID为0的用户(自然是root) 后,就不在往下查找了——它当UID也是唯一的。
⒋[GID]用户默认的组ID,这个ID可以在文件/etc/group里查到对应的组名。
⒌[身份描述]:就是用户的身份说明,默认的是无任何说明,可人工添加。
⒍[主目录]:用户的主目录,可以使用前面介绍的命令修改。
⒎[登录shell]:用户登录时系统提供的shell,请参考前面的有关内容。
<注意>:[UID]和[GID]小于500的一般都是系统自己保留,不做普通用户和组的标识的,所以新增加的用户和组一般都是UID和GID大于500的。
◆/etc/group文件说明
下面是RH的一个group文件的例子:
root:x:0:root,hujm,hjm
bin:x:1:root,bin,daemon
daemon:x:2:root,bin,daemon
sys:x:3:root,bin,adm
adm:x:4:root,adm,daemon
tty:x:5:
disk:x:6:root
lp:x:7:daemon,lp
mem:x:8:
kmem:x:9:
wheel:x:10:root
mail:x:12:mail
news:x:13:news
uucp:x:14:uucp
……
hujm:x:503:root,mynoshell,hjm
mysql:x:101:
mynoshell:x:505:
ftpusers:x:506:
它总共分四个部分:
[组名]:[密码域]:[GID]:[组员列表]
意思非常明显,需要说明一下的是,由于组一般都不用密码保护,所以虽然看起来密码域有个X字符,其实那只表示使用了SHADOW(对应文件为 gshadow)。组员列表用逗号分隔各个帐号。另外,一个组的组员如果默认登录组就是它的话,那么在组员列表里将不显示这个组员的帐号,例如用如下命令 增加的用户:
useradd -g ftpusers floatboat
在/etc/group文件里ftpusers的组员列表将不显示这个组员(真是失败),而只是在passwd文件中的GID被设置为506。而使用如下命令:
usermod -G ftpusers,mysql,webusers floatboat
就可以看见相关的组后边加上了floatboat帐号。当然,你可以直接用vi来直接编辑这个文件。
group文件和passwd文件是通过GID联系在一起的,这有点象关系数据库。根据passwd文件中一个帐户的GID,可以在group 文件中 找到对应的组名。如果采用了用户私有组机制的话,那么一般新增一个帐号,就会有对应的一个与帐号同名的组增加到group文件中。虽然这时passwd文 件中具有唯一性的[用户名]字段和group文件中具有唯一性的[组名]字段一样,并不代表着它们是通过这两个字段形成一一对应的关系的。千万别忘记,系 统对数字(UID,GID)更加敏感.
转自:http://blog.csdn.net/yetyongjin/article/details/7476860
这两天做asterisk的性能测试,经常碰到这样的错误:“Too many open files”。 我们知道,Linux下,文件描述符就是一个简单的整数值,习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。POSIX 定义了STDIN_FILENO、STDOUT_FILENO 和STDERR_FILENO 来代替 0、1、2。这三个符号常量的定义位于头文件 unistd.h。
文件描述符的有效范围是0 到OPEN_MAX。那么一个进程最多到底能打开多少个文件描述符呢?下面,就以asterisk进程为例来说明。
#ps –ef | grep asterisk
![](http://my.csdn.net/uploads/201204/19/1334806915_6581.jpg)
从第二列得到asterisk的PID为19488。
# cat /proc/19488/limits
![](http://my.csdn.net/uploads/201204/19/1334806942_4679.jpg)
红线一行说明asterisk进程最大能打开1024个文件描述符(不包含它的子进程或创建出来的线程)。在/proc/19488/task/目录下,详细列出了其下的子任务的情况,每个子文件夹里同样有一个limits文件,限定了各子任务的情况。
一个进程打开了几个文件描述符呢?
# ll /proc/19488/fd/
![](http://my.csdn.net/uploads/201204/19/1334806999_3990.jpg)
子目录fd中,详细列出了进程打开的每个文件描述符,同样,/proc/19488/task/XXXX/fd下也会有子任务打开的文件描述符的情况。要知道有几个,执行
# ll /proc/19488/fd/ | wc -l
怎样知道一个进程及其子进程和哪些文件有关联呢?lsof可以完成这工作。请注意,关联文件和打开文件描述符是两个不同的概念,关联文件的数量可能远远大于打开的文件描述符的数量。
# lsof | grep asterisk | wc –l
也可以用父进程的PID过滤
# lsof | grep 19488 | wc –l
我这里得到的值是9525
怎样修改文件描述符的限定呢?临时修改,可以通过ulimit。
# ulimit -SHn 2048
但是这样只能影响到当前的session,当终端重新连接或当前用户退出,配置就失效了。如果想永久变更需要编辑/etc/security/limits.conf 文件,添加如下两行:
* hard nofile 2048
* soft nofile 2048
内核参数对文件描述符也有限制,如果设置的值大于内核的限制,也是不行的:
查找file-max的内核参数:
# sysctl -a|grep file-max
更改file-max的内核参数:
# sysctl -w file-max=65535
Sysctl也是临时的,要想永久生效,可以通过更改sysctl的文件,编辑/etc/sysctl.conf文件,添加或修改以下一行:
fs.file-max=65535
需要注意的是,文件描述符的限制,不局限于这里描述的这些,还可能和进程的启动参数、用户的环境设置有关。当然,如果是进程BUG造成文件描述符没有及时关闭回收,这增大限制也只是治标,根本上还得修复BUG。
此外,lsof会列出系统中所占用的资源,但是这些资源不一定会占用打开的文件描述符(比如共享内存,信号量,消息队列,内存映射.等,虽然占用了这些资源,但不占用打开文件号),因此有可能出现cat /proc/sys/fs/file-max 的值小于lsof | wc -l。asterisk本身提供了一个启动脚本,名为safe_asterisk,脚本里面就对文件描述符做了一些设置。