2014-07-05 16:40:09 bdc789 阅读数 363
本人最近在学习《unix环境高级编程》一书,首先碰到的就是书中代码编译不过的问题。
首先是书中大部分程序都包含头文件apue.h,这是作者为了减少书中程序长度而将大多数程序需要包含的系统头文件统一包含在apue.h中,这样程序只需包含apue.h即可。另外该文件包含作者提供的出错处理例程和一些封装函数的声明。

现将解决方法记录如下(本人平台是ubuntu14.4):
1.从网上下载书中程序源码包,下面提供两个下载地址:
(http://download.csdn.net/detail/lsw_ruc/668760,http://download.csdn.net/detail/tyronedong/577141)。

2.将下载下来的压缩包解压(位于目录 :/home/sar/Src/apue.2e.tar.gz)
cd /home/sar/Src (根据自已的实际存在目录进行修改)
tar -xzvf  apue.2e.tar.gz
这会在当前目录下生成apue.2e文件夹

3.进入该文件夹
cd apue.2e
该目录中有个检测机器平台的脚本systype.sh,需要有执行权限
chmod +x systype.sh
另外根据自已的机器平台修改相应的Make.defines.***文件,例如本人是linux平台,修改Make.defines.linux
中WKDIR=/home/sar/Src/apue.2e,即修改为源码文件的位置。

4.执行sudo make

5.把头文件apue.h放到/usr/include/中,以root用户操作以下命令
cp ./include/apue.h /usr/include
cp ./lib/libapue.a /usr/lib
chmod 644  /usr/include/apue.h

6.验证:
mv fig1.3 fig1.3.c
gcc fig1.3.c -o ls -lapue 

7.编译时出现的问题:
apue源码make:/usr/include/bits/timex.h:31:7: 错误:expected ‘:’, ‘,’, ‘;’, ‘}’ or ‘__attribute__’ be的解决方法 参考:
http://blog.csdn.net/muais/article/details/6885708

在我的机器上编译时,提示ARG_MAX未定义,可以这么修改。
在apue.2e/include/apue.h中添加一行:
#define ARG_MAX 4096
打开apue.2e/threadctl/getenv1.c 和apue.2e/threadctl/getenv3.c,添加一行:
#include "apue.h"
2017-06-02 22:32:47 qq_29616441 阅读数 92

终于可以开始静下来,开始学习谢谢博客了,写的不好和不对的地方请指出。

前段时间在网上买了两本书数,《unix环境高级编程》和《unix网络编程》打算学习下unix上编程。

说正题吧,如何环境搭建。

1.下载源代码,自己百度下载吧。

2.仔细观察源代码目录很多工程,每个工程下面都有一个makefile。所以我们也可以效仿写一个makefile在自己的工程目录下面。微笑

3.

ROOT = /Users/huangjing/Downloads/apue.3e //你下载源码的地址
PLATFORM = $(shell $(ROOT)/systype.sh) //执行shell脚本,获取平台相关的信息
include $(ROOT)/Make.defines.$(PLATFORM) //include平台相关的顶层makefile


PROGS = test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 test12 test13 test14 test15 test16 test17 test18 test19 test20 //写的一些实例名称
all: $(PROGS) setfl.o //添加依赖关系的中间.o文件


%: %.c $(LIBAPUE)
$(CC) $(CFLAGS) $@.c -o $@ $(LDFLAGS) $(LDLIBS)


clean: //删除.o文件
rm -f $(PROGS) $(TEMPFILES) *.o file.hole


include $(ROOT)/Make.libapue.inc

2018-11-16 18:51:05 ANvDbg 阅读数 33

1 线程基本概念

典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事。有了多个控制线程之后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个线程各自处理独立的任务。
每个线程都包含有表示执行环境所必须的信息,其中包括进程中标识线程的线程ID一组寄存器值调度优先级和策略信号屏蔽字errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码程序的全局内存堆内存以及文件描述符

2 线程标识

如同每个进程都有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中时唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。
下面的函数用来对两个线程的ID进行比较。

#include <pthread.h>
// return nonzero value if two thread IDs equal or 0 if not.
int pthread_equal(pthread_t t1, pthread_t t2);

下面的函数用来获取自身的线程ID。

#include <pthread.h>
pthread_t pthread_self(void);

3 线程创建

#include <pthread.h>
int pthread_create(
    pthread_t* thread,
    const pthread_attr_t* attr,
    void*(*start_routine)(void*),
    void* arg
);

当pthread_create函数成功返回时,新创建的线程的线程ID会被设置成thread指向的内存单元。attr参数用于定制各种不同的线程属性。
新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要传入的参数有一个以上,需要做成结构体,传入arg。
线程创建时不能保证哪个线程先执行:新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。

4 线程终止

如果进程中的任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流:
[1] 线程可以简单地从启动例程中返回,返回值时线程的退出码。
[2] 线程可以被同一进程中的其他线程取消。
[3] 线程调用pthread_exit。

#include <pthread.h>
void pthread_exit(void* retval);

retval参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join函数访问到这个指针。

#include <pthread.h>
int pthread_join(pthread_t thread, void** retval);

可以通过调用pthread_join自动把线程置于分离状态1
线程可以通过调用pthread_cancel函数来请求取消统一进程中的其他线程。

#include <pthread.h>
int pthread_cancel(pthread_t thread);

线程可以安排它退出时需要调用的函数,与进程退出时可以调用atexit函数类似,这样的函数称为线程清理处理程序。

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

如果线程时通过从它的启动例程中返回而终止时,它的清理函数就不会被调用。

总结进程和线程函数之间的相似之处。

进程原语 线程原语 描述
fork pthread_create 创建
exit pthread_exit 退出
waitpid pthread_join 等待并获得退出状态
atexit pthread_cancel_push 注册退出时调用的函数
getpid pthread_self 获得ID
abort pthread_cancel 请求非正常退出
#include <pthread.h>
int pthread_detach(pthread_t thread);

5 线程同步

5.1 互斥量

可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量mutex本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问结束后释放(解锁)互斥量。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。
互斥变量是用pthread_mutex_t数据类型表示的。在使用互斥变量以前,必须首先对它进行初始化,可以把他设置为常量PTHREAD_MUTEX_INITIALIZER(只使用于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量,则在释放内存前需要调用pthread_mutex_destroy。

#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr* restrict attr);

对互斥量进行加锁,需要调用pthread_mutex_lock。解锁需要调用pthread_mutex_unlock,若不希望线程阻塞,可以使用pthread_mutex_trylock,如果可以加锁,则锁住并返回0,否则返回EBUSY。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock与pthread_mutex_lock是基本等价的,但是在达到超时时间时,pthread_mutex_timedlock不会对互斥量进行加锁,而是返回错误码ETIMEOUT。

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict abs_timeout);

5.2 读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。一次只有一个线程可以占有写锁,但可以由多个线程同时占有读锁。
当附加了写锁后,无论读锁还是写锁都会被阻塞。当附加了读锁后,写锁会被阻塞。
与互斥量相比,读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁。

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr);

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int phtread_rwlock_trywrlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);

5.3 带有超时的读写锁

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t* restrict rwlock, const struct timespce* restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict abs_timeout);

5.4 条件变量

条件变量是线程可用的另一种同步机制。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
在使用条件变量之前,必须先对它进行初始化。由pthread_cond_t数据类型表示的条件变量可以由两种方式初始化:常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,动态分配需要使用pthread_cond_init,使用pthread_cond_destroy函数释放。
使用pthread_cond_wait等待条件变量变为真,pthread_cond_timedwait可以设置一个超时值,超时返回一个错误码。

#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);
int pthread_cond_destroy(pthread_cond_t* cond);

int pthread_cond_timewait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex, const struct timespec* restrict abstime);
int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);

pthread_cond_signal函数至少能唤醒一个等待该条件的线程,pthread_cond_broadcast函数能唤醒等待该条件的全部线程。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);
int pthreda_cond_broadcast(pthread_cond_t* cond);

在调用pthread_cond_signal或者pthread_cond_broadcast时,我们认为这是在给线程或者条件发信号,一定要注意,一定要在改变条件状态之后再给线程发信号

5.5 自旋锁

自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短而且线程并不希望在重新调度上花费太多的成本

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t* lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t* lock);

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

5.6 屏障

屏障是多用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。pthread_join就是一种屏障,允许一个线程等待,直到另一个线程退出。

#include <pthread.h>
int pthread_barrier_init(
    pthread_barrier_t* restrict barrier,
    const pthread_barrierattr_t* restrict attr,
    unsigned count
);
int pthread_barrier_destroy(pthread_barrier_t* barrier);

int pthread_barrier_wait(pthread_barrier_t* barrier);

  1. 在默认情况下,线程的终止状态会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层资源可以在线程终止时立即被回收。在线程被分离后,我们不能用pthread_join函数等待它的终止状态,可以调用pthread_detach分离线程。 ↩︎

2016-06-16 20:44:22 xdyzyh 阅读数 296

4.11 函数

chown,fchown,fchownat,lchown
下面几个chown函数可以改变文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-1,则对应的ID不变。
#include<unistd.h>
int chown(const char*pathname,uid_t owner,git_t group);
int fchown(int fd,uid_t owner,gid_t group);
int fchownat(int fd,const char*pathname,uid_t owner,git_t group,int flag);
int lchown(const char*pathname,uid_t owner,gid_t group);
注意:显然,fchown不可能更改符号链接的属主和属组,但是fchownat可以通过设置参数flag为AT_SYMLINK_NOFOLLOW参数来改变符号链接的属主和属组。
注意:如果常亮_POSIX_CHOWN_RESTRICTED已经定义,那么,
    (1)只有超级用户进程能够更改该文件的属主ID。
    (2)如果进程拥有此文件(其有效用户ID=该文件的属主ID),参数owner=-1或文件的属主ID,并且参数group等于进程的有效组ID或进程的附属组ID之一,那么一个非超级用户进程可以更改该文件的属组ID。
    这意味着,不能更改其他用户的文件的属主ID,你可以更改你所拥有的文件的组ID,但是只能更改到你所属的组。
//写个程序测试以上规定:
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){
  //创建一个文件,打印该文件的属主和属组
  int fd=creat("aa.txt",0666);
  if(fd<0){
    printf("文件aa.txt创建失败\n");
    exit(0);
  }
  struct stat st;
  if(fstat(fd,&st)<0){
    printf("获取aa.txt文件的属性失败\n");
    exit(0);
  }
  printf("aa.txt的文件属主是:%d,属组是:%d\n",st.st_uid,st.st_gid);
  //现在更改此文件的属主ID,不同用户不拥有更改属主的权限,预计更改失败
  if(fchown(fd,1000,-1)<0){
    printf("不能将文件更改成属主为xcl,只有超级管理员才具有属主的更改权限\n");
  }else{
    printf("您的有效用户ID必定是root,文件aa.txt的属主成功更改为xcl\n");
  }
  //更改文件的属组,若设置了sgid,suid位,则更改属主的必定成功,并且下面的也必定成功
  if(fchown(fd,-1,1000)<0){
    printf("不能将属组改成xcl所在组,超级管理员,或者如果你拥有aa.txt并且您只能更改到您的有效组\n");
  }else{
    printf("您拥有此文件,并且将此文件的属组更改为您的有效组了,更改成功\n");
  }
  if(fstat(fd,&st)<0){
    printf("获取aa.txt文件的属性失败\n");
    exit(0);
  }
  printf("aa.txt的文件属主是:%d,属组是:%d\n",st.st_uid,st.st_gid);
  return 0;
}
//测试结果如下:
oracle@xcl:~/桌面$ ll main
-rwsr-xr-x 1 root root 10864  616 12:38 main*
oracle@xcl:~/桌面$ ./main
aa.txt的文件属主是:0,属组是:1001
您的有效用户ID必定是root,文件aa.txt的属主成功更改为xcl
您拥有此文件,并且将此文件的属组更改为您的有效组了,更改成功
aa.txt的文件属主是:1000,属组是:1000
oracle@xcl:~/桌面$

4.12 文件长度

stat结构成员st_size表一以字节为单位的文件的长度。此字段只对普通文件,目录文件和符号连接有意义。
注意:对于符号连接,文件长度是文件名中的实际字节数。
//编写程序,测试符号链接的文件长度
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){

  struct stat st;
  if(lstat("w",&st)<0){
    printf("获取w的属性失败\n");
    exit(0);
  }
  printf("w符号链接的文件大小是:%ld\n",st.st_size);

  return 0;
}
//测试结果如下:
xcl@xcl:~/桌面$ ./main
w符号链接的文件大小是:13
xcl@xcl:~/桌面$ ll w
lrwxrwxrwx 1 xcl xcl 13  616 12:53 w -> /etc/password
xcl@xcl:~/桌面$

4.13 文件截断

#include<unistd.h>
int truncate(const char*pathname,off_t length);
int ftruncate(int fd,off_t length);
这两个函数将现有文件阶段为指定的长度length,如果length小于文件长度,则length长度以后的字节都将截断,如果length大于文件长度,那么文件长度将增加,并且增加的部分将会是一个空洞。
//证明truncate会导致文件空洞
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){

  if(truncate("nohole",4096)<0){
    printf("文件阶段失败\n");
    exit(0);
  }
  return 0;
}
//结果如下:
xcl@xcl:~/桌面$ ll nohole
-rw-rw-r-- 1 xcl xcl 14  6月 16 13:08 nohole
xcl@xcl:~/桌面$ gcc -g -o main ./xcl.c
xcl@xcl:~/桌面$ ./main
xcl@xcl:~/桌面$ ll nohole
-rw-rw-r-- 1 xcl xcl 4096  6月 16 13:16 nohole
xcl@xcl:~/桌面$

4.15 函数link,linkat,unlink,unlinkat,remove

----------------------------------文件的硬链接的创建和删除----------------------------------------
#include<unistd.h>
int link(const char*pathname,const char*newpath);
int linkat(int efd,const char*existingpath,int nfd,const char*newpath,int flag);
注意:有很多文件系统实现不允许对目录的硬链接
注意:linkat的flag可以取三个值:0,AT_EMPTY_PATH,AT_SYMLINK_FOLLOW
#include<unistd.h>
int unlink(const char*pathname);
int unlinkat(int fd,const char*pathname,int flag);
默认unlinkat是删除文件,当flag设置了AT_REMOVEDIR的时候,unlinkat删除目录。
注意:flag参数给出了一种方法,使调用进程可以改变unlinkat函数的默认行为,当AT_REMOVEDIR标志被设置的时候,unlinkat函数可以类似于rmdir一样删除目录,如果这个标志被清除了,unlinkat与unlink执行同样的操作。
注意:这两个函数删除目录项,并允许pathname所引用的文件的链接计数减1。如果对该文件还有其他链接,则仍可通过其他连接访问该文件的数据。
注意:为了接触对文件的链接,必须对包含该目录项的目录具有写和执行权限,正如4.10所述,如果该目录还设置了粘连位S_ISVTX,那么必须对该目录具有写权限,并且满足下面三个条件之一:
    拥有该目录
    拥有该文件
    拥有超级管理员权限
只有当链接计数为0的时候该文件的内容才被删除。另一个条件也会阻止删除文件的内容,只要有进程打开了文件,其内容页不能被删除。关闭一个文件时,内核首先检查打开文件的进程个数,如果这个计数达到0,内核再去检查其链接计数,如果计数为0,那么就删除该文件的内容:要删除文件时,先检查打开该文件的进程计数,再检查链接计数,只有都为0才会删除该文件。
//编写一个程序,首先获取文件nohole的i节点编号,然后建立一个硬链接并获取该i节点编号,之后删除nohole文件
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){

  struct stat st;
  if(stat("nohole",&st)<0){
    printf("获取nohole的属性出错\n");
    exit(0);
  }
  printf("nohole的i节点编号是:%ld\n",st.st_ino);
  if(link("nohole","mynohole")<0){
    printf("创建链接错误\n");
    exit(0);
  }else{
    printf("成功创建链接\n");
    if(stat("mynohole",&st)<0){
      printf("获取mynohole信息失败\n");
      exit(0);
    }else{
      printf("mynohole的i节点是:%ld\n",st.st_ino);
    }
  }
  if(unlink("nohole")<0){
    printf("删除nohole失败\n");
    exit(0);
  }else{
    printf("删除链接成功\n");
  }

  return 0;
}
//结果如下:
xcl@xcl:~/桌面$ ./main
nohole的i节点编号是:26490467
成功创建链接
mynohole的i节点是:26490467
删除链接成功
------------------------
注意:进程调用open或creat创建一个文件,然后立即调用unlink,因为该文件仍旧是打开的,所以不会将其内容删除。只有进程关闭文件或终止时,该文件的内容才会被删除。
注意:如果pathname是符号链接,那么unlink删除该符号链接,而不是由该符号链接指向的文件,给出符号链接的情况下,没有一个函数能够删除由该符号链接引用的文件。
注意:如果文件系统支持的话,超级用户可以调用unlink,其参数指定一个目录,但是通常应该使用rmdir函数,而不是使用unlink方式。

#include<stdio.h>
int remove(const char*pathname);
-------------------------------------
对于文件remove和unlink相同,对于目录remove和rmdir相同。

4.16 函数rename和renameat

文件或目录可以用rename或renameat函数进行重命名。
#include<stdio.h>
int rename(const char*oldname,const char*newname);
int renameat(int oldfd,const char*oldname,int newfd,const char*newname);

//将目录bb命名为空目录aa
#include<strings.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){

  if(rename("bb","aa")<0){
    printf("重命名非空目录bb为aa失败,其中aa是已经存在的空目录\n");
    exit(0);
  }else{
    printf("重命名成功\n");
  }
  return 0;
}
//将非空目录重命名为非空目录将会失败
#include<strings.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(int argc,char**argv){

  if(rename("bb","aa")<0){
    printf("重命名非空目录bb为aa失败,其中aa是已经存在的非空目录\n");
    exit(0);
  }else{
    printf("重命名成功\n");
  }
  return 0;
}
注意:rename是不跟随符号连接的
(1) 如果oldname指的是一个文件而不是目录,那么为该文件或符号连接重命名。
(2) 如果oldname值得是一个目录,那么为该目录重命名。
(3) 如果oldname或newname引用一个符号链接,则处理的是符号链接本身,而不是它引用的文件。
(4) 不能对.和..重命名。
(5) 如果oldname和newname相同,则直接返回。
(6) 如果newname已经存在,则调用进程对他需要有写权限,另外,调用进程将删除oldname目录项,并可能要创建newname项,所以进程需要对包含oldname和newname的目录具有写和执行权限。
2018-11-12 12:33:45 ANvDbg 阅读数 91

1 引言

上文围绕了普通文件I/O进行了讨论——打开文件、读文件或写文件。本文将描述文件系统的其他特征和文件的性质。将从stat函数开始,stat结构中的大多数成员都是基本系统数据类型,逐个分解stat结构的每一个成员以了解文件的所有属性。
使用stat函数最多的地方可能就是[ls -ls]命令,可以获得一个文件的全部信息。
本文主要讨论4个stat函数以及它们的返回信息。

struct stat
{
    dev_t     st_dev;         /* ID of device containing file */
    ino_t     st_ino;         /* inode number */
    mode_t    st_mode;        /* file type and mode */
    nlink_t   st_nlink;       /* number of hard links */
    uid_t     st_uid;         /* user ID of owner */
    gid_t     st_gid;         /* group ID of owner */
    dev_t     st_rdev;        /* device ID (if special file) */
    off_t     st_size;        /* total size, in bytes */
    blksize_t st_blksize;     /* blocksize for filesystem I/O */
    blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

    /* Since Linux 2.6, the kernel supports nanosecond
     * precision for the following timestamp fields.
     * For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* time of last access */
    struct timespec st_mtim;  /* time of last modification */
    struct timespec st_ctim;  /* time of last status change */

#define st_atime st_atim.tv_sec      /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
// return 0 or -1 if an error occurred.
int stat(const char* pathname, struct stat* buf);
int fstat(int fd, struct stat* buf);
int lstat(const char* pathname, struct stat* buf);

#include <fcntl.h>
#include <sys/stat.h>
// return 0 or -1 if an error occurred.
int fstatat(int dirfd, const char* pathname, struct stat* buf, int flags);

一旦给出pathname,stat函数将返回与此文件有关的信息结构fstat函数获得已在描述符fd上打开文件的有关信息lstat函数类似于stat但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息
fstatat函数为一个相对于当前打开目录(由fd参数指定)的路径名返回文件统计信息。flag参数控制着是否跟随着一个符号链接。当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而是返回符号链接本身的信息。如果fd参数的值是AT_FDCWD,并且pathname参数是一个相对路径名,fstatat会计算相对于当前目录的pathname参数;如果pathname参数是一个绝对路径,fd参数就会被忽略。这两种情况下,fstatat的作用就跟stat或lstat一样。

2 文件类型

文件类型包含如下几种:
[1] 普通文件
[2] 目录文件
[3] 块特殊文件:提供对设备带缓冲的访问,每次访问以固定长度为单位进行
[4] 字符特殊文件:提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件
[5] FIFO:这种类型的文件用于进程间通信,有时也成为命名管道
[6] 套接字:这种类型的文件用于进程间的网路通信,也可用于在一台宿主机上进程之间的非网络通信
[7] 符号链接:这种类型的文件指向另一个文件

文件类型信息保存在stat结构的st_mode成员中,下表中的宏参数都是stat结构中st_mode成员的可取值。

文件类型
S_ISREG() 普通文件
S_ISDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 管道或FIFO
S_ISLNK() 符号链接
S_ISSOCK() 套接字

3 文件ID和组ID

3.1 设置用户ID和设置组ID

与一个进程相关联的ID有6个或更多,包括:
实际用户ID实际组ID:标识我们究竟是谁;
有效用户ID有效组ID附属组ID:决定了我们的文件访问权限;
保存的设置用户ID保存的设置组ID:在执行一个程序时包含了有效用户ID和有效组ID的副本。
通常:有效用户ID等于实际用户ID,有效组ID等于实际组ID。

每个文件有一个所有者和组所有者,所有者由stat结构中的st_uid指定,组所有者由st_gid指定。
设置用户ID位以及设置组ID位都包含在文件的st_mode值中,这两位可分别用常量S_ISUID和S_ISGID测试。

3.2 更改文件的用户ID和组ID

下面几个chown函数可用于更改文件的用户ID和组ID。

#include <unistd.h>
// return 0 or -1 if an error occurred.
int chown(const char* path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char* path, uid_t owner, gid_t group);

#include <fcntl.h>
#include <unistd.h>
// return 0 or -1 if an error occurred.
int fchownat(int dirfd, const char* pathname, uid_t owner, gid_t group, int flags);

除了所引用文件为符号链接外,这4个函数的操作类似。在符号链接情况下,lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是符号链接所指向的文件的所有者。
fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号链接的所有者。

4 文件访问权限

st_mode值也包含了对文件的访问权限位。当提及文件时,指的是前面所提到的任何类型的文件。所有文件类型都有访问权限。
每个文件有9个访问权限位,可分为三类。

st_mode屏蔽 含义
S_IRUSR 用户读
S_IWUSR 用户写
S_IXUSR 用户执行
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行

chmod命令可以修改这9个权限位,用u表示用户,用g表示组,用o表示其他。

4.1 访问权限测试

当用open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。有时,进程也希望按其实际用户ID和实际组ID来测试其访问能力。
access和fassessat函数是按实际用户ID和实际组ID进行访问权限测试的。

#include <unistd.h>
// return 0 or -1 if an error occurred.
int access(const char* pathname, int mode);

#include <fcntl.h>
#include <unistd.h>
// return 0 or -1 if an error occurred.
int faccessat(int dirfd, const char* pathname, int mode, int flags);

测试文件是否存在,mode位F_OK;否则是下表所列常量的按位或

mode 说明
R_OK 测试读权限
W_OK 测试写权限
X_OK 测试执行权限

flag参数可以用于改变faccessat的行为,如果flag设置位AT_EACCESS,访问检查用的时调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。

4.2 为进程设置文件模式创建屏蔽字

#include <sys/types.h>
#include <sys/stat.h>
// Always succeeds and return previous value of the mask.
mode_t umask(mode_t mask);

4.3 更改现有文件的访问权限

#include <sys/stat.h>
// return 0 or -1 if an error occurred.
int chmod(const char* path, mode_t mode);
int fchmod(int fd, mode_t mode);

#include <fcntl.h>
#include <sys/stat.h>
// return 0 or -1 if an error occurred.
int fchmodat(int dirfd, const char* pathname, mode_t mode, int flags);

chmod函数在指定的文件上进行操作。
fchmod函数对已打开的文件进行操作。

4.4 文件访问权限小结

常量 说明 对普通文件的影响 对目录的影响
S_ISUID 设置ID 执行时设置有效用户ID 未使用
S_ISGID 设置组ID 若组执行位设置则执行设置有效组ID,否则使强制性锁起作用(若支持) 将在目录中创建的新文件的组ID设置为目录的组ID
S_ISVTX 黏着位 在交换区缓存程序正文(若支持) 限止在目录中删除和重命名文件
S_IRUSR 用户读 许可用户读文件 许可用户读目录项
S_IWUSR 用户写 许可用户读文件 许可用户在目录中删除和创建文件
S_IXUSR 用户执行 许可用户读文件 许可用户在目录中搜索给定路径名
S_IRGRP 组读 许可组读文件 许可组读目录项
S_IWGRP 组写 许可组读文件 许可组在目录中删除和创建文件
S_IXGRP 组执行 许可组读文件 许可组在目录中搜索给定路径名
S_IROTH 其他读 许可其他读文件 许可其他读目录项
S_IWOTH 其他写 许可其他读文件 许可其他在目录中删除和创建文件
S_IXOTN 其他执行 许可其他读文件 许可其他在目录中搜索给定路径名

S_IRWXU = S_IRUSR | S_IWUSR | S_IXUSR
S_IRWXG = S_IRGRP | S_IWGRP | S_IXGRP
S_IRWXO = S_IROTH | S_IWOTH | S_IXOTH

文件系统

符号链接

文件时间

目录

设备特殊文件

没有更多推荐了,返回首页