t权限(粘滞位):
是‘不可删除’权限,就是说即使某用户拥有这个文件的rwx权限,可以随意修改文件内容,但是就是不能删除,甚至不能修改文件名,只有root才行。t权限也可以直接用 chmod o+t/a+t filename 和chmod -t filename 来修改。
只针对目录生效,它表示只能让所属主以及root可以删除(重命名/移动)该目录下的文件。比如/tmp目录本来就是任何用户都可以读写,如果别人可以任意删除(重命名/移动)自己的文件,那岂不是很危险,所以这个t权限就是为了解决这个问题。
下面通过一个实例来体会这个t权限的用法:
(1) root用户在/tmp目录下创建一个test目录,并设置test目录的相关权限为1777(有特殊权限t)
[root@localhost tmp]# mkdir test
[root@localhost tmp]# chmod1777 test
[root@localhost tmp]# ls -ld test
drwxrwxrwt. 2 root root 4096 Oct 1222:31 test
(2) 切换到第一个用户zhangming,在test目录下创建一个新文件aaa.txt,并写入数据
[root@localhost tmp]# su zhangming
[zhangming@localhost tmp]$ touch test/aaa.txt
[zhangming@localhost tmp]$ echo"hello" >> test/aaa.txt
[zhangming@localhost tmp]$ ls -l test
total 4
-rw-rw-r--. 1 zhangming zhangming 6 Oct 1222:34 aaa.txt
(3) 切换到第二个用户shuihuo379,尝试删除zhangming用户创建的文件aaa.txt,此时提示无法删除
[zhangming@localhost tmp]$ su shuihuo379
[shuihuo379@localhost tmp]$ ls -l test/aaa.txt-rw-rw-r--. 1 zhangming zhangming 6 Oct 1222:34 test/aaa.txt
[shuihuo379@localhost tmp]$ rm test/aaa.txtrm: remove write-protected regular file `test/aaa.txt'? yrm: cannot remove `test/aaa.txt': Operation not permitted
(4) 重新切换到root用户,执行删除权限位t操作
[shuihuo379@localhost tmp]$ su
[root@localhost tmp]# chmod -t test
[root@localhost tmp]# ls -ld test
drwxrwxrwx. 2 root root 4096 Oct 1222:33 test
(5) 再次切换到用户shuihuo379,尝试删除zhangming用户创建的文件aaa.txt,此时删除成功,zhangming用户创建的文件aaa.txt已经不存在了
[root@localhost tmp]# su shuihuo379
[shuihuo379@localhost tmp]$ ls -l test
total 4
-rw-rw-r--. 1 zhangming zhangming 6 Oct 1222:34 aaa.txt
[shuihuo379@localhost tmp]$ rm test/aaa.txtrm: remove write-protected regular file `test/aaa.txt'? y
[shuihuo379@localhost tmp]$ ls -l test
total 0
粘滞位(粘着位)
上面所说的t权限就是我们在这里要讲的粘滞位(sticky bit),我们给刚刚的cur目录采用chmod o+t的方式给other用户设置粘滞位。
然后我们继续切换到dh用户,看看我们能否继续之前的删除操作:
可以看到此时我们是没有权限删除root用户创建的文件了,这也就是粘滞位的作用。
粘滞位权限便是针对此种情况设置,当⽬录被设置了粘滞位权限以后,即便⽤户对该⽬录有写⼊权限,也不能删除该⽬录中其他⽤户的⽂件数据,⽽是只有该⽂件的所有者和root⽤户才有权将其删除。设置了粘滞位之后,正好可以保持⼀种动态的平衡:允许各⽤户在⽬录中任意写⼊、删除数据,但是禁⽌随意删除其他⽤户的数据。
几点说明
对于特殊权限的添加是添加在原有的执行权限上的,所以特殊权限添加的要求需要文件或者目录本身具有可执行权限。
上图中,我去掉了cur的other的执行权限,可以看到本来't'的位置变成了'T',此时dh用户在cur目录中是不具有权限来进行一系列操作的。
那么原来的执行标志x到哪里去了呢? 系统是这样规定的, 假如本来在该位上有x, 则这些特别标志 (suid, sgid, sticky) 显示为小写字母 (s, s, t).否则, 显示为大写字母 (S, S, T) 。
注意事项
粘滞位权限是针对目录的,对文件无效
上述的这些操作是在root用户下创建了一个test.c文件,然后添加了t权限,然而在dh用户下还是可以直接进行删除的。所以粘滞位是针对有执行权限的目录的,对于文件添加粘滞位并没有什么作用。
2.粘滞位权限(sticky)
作用:设置粘滞位权限后,即便用户和对目录有写入权限,也不能删除该目录中其他用户的文件
应用场景:对于公共可写目录,用户可创建删除自己的文件,但是不能删除其他用户的文件
表现形式:sticky表示为文件其它用户执行权限位上的t或T:
x: t -:T
若之前在其它用户执行位已有执行权限,则显示为t,否则显示为T
使用,如chmod o+t filename ...
chmodo-t filename ...
数字1表示增加粘滞位权限;数字0表示取消粘滞位权限;
使用,如:chmod 1755 filename ...
实例演示:(以上一次操作为前提)
(1)对/tmp/dirtest/设置粘滞位权限
1.[root@Liu ~]# chmod o+t /tmp/dirtest/
2.[root@Liu ~]# ll -d /tmp/dirtest/
3.drwxrwxr-t 2 root cloud 4096 Mar 1202:58 /tmp/dirtest/
(2)测试用户是否能删除非自己创建的文件
1.[docker@Liu dirtest]$touch
docker.txt
2.[docker@Liu dirtest]$rm
-rfopenstack.txt
3.rm: cannot remove `openstack.txt':Operation not permitted
4.[openstack@Liu dirtest]$touch
openstack.txt
5.[openstack@Liu dirtest]$rm
-rfdocker.txt
6.rm: cannot remove `docker.txt':Operation not permitted
答案是否定的。
这样的话,用户可以在此目录中随意创建文件和目录,但是不能删除非自己创建的文件或目录
所以,最终解决方案是:
1.[root@Liu ~]# useradd openstack
2.[root@Liu ~]# useradd docker
3.[root@Liu ~]# groupadd cloud
4.[root@Liu ~]# gpasswd -Mopenstack,docker cloud
5.[root@Liu ~]# chmod g+w /tmp/test/
6.
[root@Liu ~]# chown .cloud/tmp/test
7.[root@Liu ~]# chmod g+s /tmp/test/
8.[root@Liu ~]# chmod o+t /tmp/test/
这样,openstack和docker用户都可以git目录中创建文件,修改自己以及对方的文件,但是不能删除对方的文件。如果连对方的文件都不允许修改,只要chmod g-s /tmp/test即可。
File_struct:
在具体介绍这几个结构以前,我们需要解释一下文件描述符、打开的文件描述、系统打开文件表、用户打开文件表的概念以及它们的联系。
1.文件对象
在Linux中,进程是通过文件描述符(file descriptors,简称fd)而不是文件名来访问文件的,文件描述符实际上是一个整数。Linux中规定每个进程能最多能同时使用NR_OPEN个文件描述符,这个值在fs.h中定义,为1024*1024(2.0版中仅定义为256)。
每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改。Linux中专门用了一个数据结构file来保存打开文件的文件位置,这个结构称为打开的文件描述(openfile description)。这个数据结构的设置是煞费苦心的,因为它与进程的联系非常紧密,可以说这是VFS中一个比较难于理解的数据结构。
首先,为什么不把文件位置干脆存放在索引节点中,而要多此一举,设一个新的数据结构呢?我们知道,Linux中的文件是能够共享的,假如把文件位置存放在索引节点中,则如果有两个或更多个进程同时打开同一个文件时,它们将去访问同一个索引节点,于是一个进程的LSEEK操作将影响到另一个进程的读操作,这显然是不允许也是不可想象的。
另一个想法是既然进程是通过文件描述符访问文件的,为什么不用一个与文件描述符数组相平行的数组来保存每个打开文件的文件位置?这个想法也是不能实现的,原因就在于在生成一个新进程时,子进程要共享父进程的所有信息,包括文件描述符数组。
我们知道,一个文件不仅可以被不同的进程分别打开,而且也可以被同一个进程先后多次打开。一个进程如果先后多次打开同一个文件,则每一次打开都要分配一个新的文件描述符,并且指向一个新的file结构,尽管它们都指向同一个索引节点,但是,如果一个子进程不和父进程共享同一个file结构,而是也如上面一样,分配一个新的file结构,会出现什么情况了?让我们来看一个例子:
假设有一个输出重定位到某文件A的shellscript(shell脚本),我们知道,shell是作为一个进程运行的,当它生成第一个子进程时,将以0作为A的文件位置开始输出,假设输出了2K的数据,则现在文件位置为2K。然后,shell继续读取脚本,生成另一个子进程,它要共享shell的file结构,也就是共享文件位置,所以第二个进程的文件位置是2K,将接着第一个进程输出内容的后面输出。如果shell不和子进程共享文件位置,则第二个进程就有可能重写第一个进程的输出了,这显然不是希望得到的结果。
至此,已经可以看出设置file结构的原因所在了。
file结构中主要保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表,其最大长度是NR_FILE,在fs.h中定义为8192。
file结构在include\linux\fs.h中定义如下:
structfile
{
struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
structfile_operations *f_op; /*指向文件操作表的指针*/
mode_tf_mode; /*文件的打开模式*/
loff_tf_pos; /*文件的当前位置*/
unsignedshort f_flags; /*打开文件时所指定的标志*/
unsignedshort f_count; /*使用该结构的进程数*/
unsignedlong f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及
预读的页面数*/
intf_owner; /* 通过信号进行异步I/O数据的传送*/
unsignedint f_uid, f_gid; /*用户的UID和GID*/
intf_error; /*网络写操作的错误码*/
unsignedlong f_version; /*版本号*/
void*private_data; /* tty驱动程序所需 */
};
每个文件对象总是包含在下列的一个双向循环链表之中:
·“未使用”文件对象的链表。该链表既可以用做文件对象的内存高速缓存,又可以当作超级用户的备用存储器,也就是说,即使系统的动态内存用完,也允许超级用户打开文件。由于这些对象是未使用的,它们的f_count域是NULL,该链表首元素的地址存放在变量free_list中,内核必须确认该链表总是至少包含NR_RESERVED_FILES个对象,通常该值设为10。
·“正在使用”文件对的象链表:该链表中的每个元素至少由一个进程使用,因此,各个元素的f_count域不会为NULL,该链表中第一个元素的地址存放在变量anon_list中。
如果VFS需要分配一个新的文件对象,就调用函数get_empty_filp()。该函数检测“未使用”文件对象链表的元素个数是否多于NR_RESERVED_FILES,如果是,可以为新打开的文件使用其中的一个元素;如果没有,则退回到正常的内存分配。
2.用户打开文件表
每个进程用一个files_struct结构来记录文件描述符的使用情况,这个files_struct结构称为用户打开文件表,它是进程的私有数据。files_struct结构在include/linux/sched.h中定义如下:
struct files_struct {
atomic_tcount; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
intmax_fds; /*当前文件对象的最大数*/
intmax_fdset; /*当前文件描述符的最大数*/
intnext_fd; /*已分配的文件描述符加1*/
structfile ** fd; /* 指向文件对象指针数组的指针 */
fd_set*close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set*open_fds; /*指向打开文件描述符的指针*/
fd_setclose_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
fd_set open_fds_init; /*文件描述符的初值集合*/
structfile * fd_array[32];/* 文件对象指针的初始化数组*/
};
fd域指向文件对象的指针数组。该数组的长度存放在max_fds域中。通常,fd域指向files_struct结构的fd_array域,该域包括32个文件对象指针。如果进程打开的文件数目多于32,内核就分配一个新的、更大的文件指针数组,并将其地址存放在fd域中;内核同时也更新max_fds域的值。
对于在fd数组中有入口地址的每个文件来说,数组的索引就是文件描述符(file descriptor)。通常,数组的第一个元素(索引为0)是进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件(参见图8.3)。请注意,借助于dup( )、dup2( )和 fcntl( )系统调用,两个文件描述符就可以指向同一个打开的文件,也就是说,数组的两个元素可能指向同一个文件对象。当用户使用shell结构(如2>&1)将标准错误文件重定向到标准输出文件上时,用户总能看到这一点。
open_fds域包含open_fds_init域的地址,open_fds_init域表示当前已打开文件的文件描述符的位图。max_fdset域存放位图中的位数。由于数据结构fd_set有1024位,通常不需要扩大位图的大小。不过,如果确实必须的话,内核仍能动态增加位图的大小,这非常类似文件对象的数组的情形。
当开始使用一个文件对象时调用内核提供的fget()函数。这个函数接收文件描述符fd作为参数,返回在current->files->fd[fd]中的地址,即对应文件对象的地址,如果没有任何文件与fd对应,则返回NULL。在第一种情况下,fget( )使文件对象引用计数器f_count的值增1。
当内核完成对文件对象的使用时,调用内核提供的fput() 函数。该函数将文件对象的地址作为参数,并递减文件对象引用计数器f_count的值,另外,如果这个域变为NULL,该函数就调用文件操作的“释放”方法(如果已定义),释放相应的目录项对象,并递减对应索引节点对象的i_writeaccess域的值(如果该文件是写打开),最后,将该文件对象从“正在使用”链表移到“未使用”链表。
3.关于文件系统信息的fs_struct结构
第三个结构是fs_struct,在2.4以前的版本中在include/linux/sched.h中定义为:
struct fs_struct {
atomic_tcount;
intumask;
struct dentry * root, * pwd;
};
在2.4中,单独定义在include/linux/fs_struct.h中:
struct fs_struct {
atomic_tcount;
rwlock_tlock;
intumask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, *altrootmnt;
};
count域表示共享同一fs_struct 表的进程数目。umask域由umask( )系统调用使用,用于为新创建的文件设置初始文件许可权。
fs_struct中的dentry结构是对一个目录项的描述,root、pwd及 altroot三个指针都指向这个结构。其中,root所指向的dentry结构代表着本进程所在的根目录,也就是在用户登录进入系统时所看到的根目录;pwd指向进程当前所在的目录;而altroot则是为用户设置的替换根目录。实际运行时,这三个目录不一定都在同一个文件系统中。例如,进程的根目录通常是安装于“/”节点上的Ext2文件系统,而当前工作目录可能是安装于/msdos的一个DOS文件系统。因此,fs_struct结构中的rootmnt、 pwdmnt及 altrootmnt就是对那三个目录的安装点的描述,安装点的数据结构为vfsmount。
3、验证调用多个atexit函数时的输出顺序
源码:
#include<stdio.h>
#include<stdlib.h>
void fun1()
{
printf("thefun1\n");
}
void fun2()
{
printf("thefun2\n");
}
void fun3()
{
printf("thefun3\n");
}
int main()
{
atexit(fun1);
atexit(fun2);
atexit(fun3);
printf("the main\n");
return0;
}
结果: