echo编程 linuxc
2014-04-20 21:56:50 tom555cat 阅读数 524

Client

//============================================================================
// Name        : EchoClient.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <sys/socket.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>

const char *IPADRR = "127.0.0.1";
const unsigned int SERVERPORT = 7778;
const unsigned int MAXLINE = 1024;

void doit(FILE *fp, int sockfd)
{
	int ret;
	char send_buf[MAXLINE], recv_buf[MAXLINE];
	while(fgets(send_buf, MAXLINE, fp) != NULL){
		if((ret = send(sockfd, send_buf, strlen(send_buf), 0)) == -1){
			printf("send error\n");
			return;
		}
		//每次接收数据前将recv_buf清空,主要是填充'\0',因为接收数据不会在添上'\0'为结尾
		bzero(recv_buf, MAXLINE);
		if((ret = recv(sockfd, recv_buf, MAXLINE, 0)) == 0){
			printf("server stopped!\n");
		}
		fputs(recv_buf, stdout);
	}
}

int main() {
	int clifd;
	struct sockaddr_in serveraddr;

	if((clifd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		printf("socket create error\n");
		return -1;
	}

	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(IPADRR);
	serveraddr.sin_port = htons(SERVERPORT);

	if(connect(clifd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
		printf("connect to server fail\n");
		return -1;
	}

	printf("connect to server successfully!\n");

	doit(stdin, clifd);

	return 0;
}
server

//============================================================================
// Name        : EchoServer.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

const unsigned int PORT = 7778;
const unsigned int LISTEN_NUM = 128;
const unsigned int MAXLINE = 1024;

void* doit(void *arg)
{
	pthread_detach(pthread_self());
	char buf[MAXLINE];
	int connfd = (int)arg;
	int n = 0;
	while((n = recv(connfd, buf, sizeof(buf), 0)) > 0){
		send(connfd, buf, n, 0);
	}
	if(n == -1){
		printf("receive error\n");
	}
	close(connfd);
	return NULL;
}

int main() {
	struct sockaddr_in cliaddr, serveraddr;
	int listenfd, connfd;
	socklen_t clilen;
	pthread_t tid;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(PORT);

	if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1){
		printf("bind error\n");
		return -1;
	}

	if(listen(listenfd,LISTEN_NUM) == -1){
		printf("listen error\n");
		return -1;
	}
	for(;;){
		clilen = sizeof(cliaddr);
		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
		if(connfd>0){
			if(pthread_create(&tid, NULL, doit, (void *)connfd)){
				printf("pthread create error\n");
				return -1;
			}
		}
	}

	close(listenfd);
	return 0;
}


2016-10-28 14:39:28 M_O_Bz 阅读数 242

linuxc 原文地址

main 函数

我们知道main函数的标准原型应该是如下所示

int main(int argc, char *argv[])

argc 是命令行参数的个数

argv 是一个指向指针的指针,为什么不是指针数组呢?

因为函数原型中的[]表示指针而不表示数组,等价于 char **argv

那为什么要写成char *argv[]而不写成 char **argv 呢?

这样写给读代码的人提供了有用信息,argv 不是指向单个指针,而是指向一个指针数组的首元素

数组中每个元素都是 char *指针, 指向一个命令行参数字符串

inotify and epoll

dir and base name

gcc -o inotify inotify.c
mkdir tmp
./inotify tmp &
echo > tmp/1
echo > tmp/2
rm tmp/1 tmp/2
gcc -o epoll epoll.c
mkdir tmp
mkfifo tmp/1 tmp/2 tmp/3
./epoll tmp/1 tmp/2 tmp/3 &
echo aaa > tmp/1
echo bbb > tmp/2
gcc -o inotify_epoll inotify_epoll.c
mkdir tmp
./inotify_epoll tmp/ &
mkfifo tmp/1 tmp/2 tmp/3
echo aaa > tmp/1
echo bbb > tmp/2
rm tmp/3

内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号.
由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

  1. 用户程序注册了 SIGQUIT 信号的处理函数 sighandler.
  2. 当前正在执行 main 函数,这时发生中断或异常切换到内核态.
  3. 在中断处理完毕后要返回用户态的 main 函数之前检查到有信号 SIGQUIT 递达.
  4. 内核决定返回用户态后不是恢复 main 函数的上下文继续执行,而是执行 sighandler 函数,sighandler 和 main 函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
  5. sighandler 函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。
  6. 如果没有新的信号要递达,这次再返回用户态就是恢复 main 函数的上下文继续执行了。

如下图所示:

kernel catch signal

使用二级指针巧妙删除链表节点

/*
 * delete 的优化版本
 * 不需要对是否是删除头部特殊判断
 * 这里巧用了二级指针操作
 * 一个指向当前节点的二级指针就是其上一节点
 */
void delete_plus(struct node *p)
{
    struct node **pnext;

    for (pnext = &head; *pnext; pnext = &(*pnext)->next)
    {
        if (*pnext == p)
        {
            *pnext = p->next;
            return;
        }
    }
}

delete

进程

  • 在各自独立的地址空间中运行
  • 共享数据需要mmap或者进程间通信机制

线程

需要在一个进程中同时执行多个控制流程就要用到线程

进程里的信号处理函数也可以,只是线程更为灵活

因为信号处理函数的控制流程只是在信号达到的时候产生

在处理完信号后就结束,多线程的控制流程可以长期并存

各线程之间共享的资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_IGN、SIG_DFL 或者自定义的信号处理函数)
  • 当前工作目录
  • 用户 id 和组 id

但有些资源是每个线程各有一份的:

  • 线程 id
  • 上下文,包括各种寄存器的值、程序计数器和栈指针
  • 栈空间
  • errno 变量
  • 信号屏蔽字
  • 调度优先级

Mutex(线程间同步)

写程序时应该尽量避免同时获得多个锁,如果一定有必要这么做,则有一个原则:

如果所有线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁, 则不会出现死锁。

比如一个程序中用到锁 1、锁 2、锁 3,它们所对应的Mutex 变量的地址
是 锁1< 锁2< 锁3,那么所有线程在需要同时获得 2 个或 3 个锁时都应该按锁1、锁2、锁 3的顺序获得。
如果要为所有的锁确定一个先后顺序比较困难,则应该尽量使用 pthread_mutex_trylock调用代替pthread_mutex_lock调用,以免死锁。

信号量(Semaphore)

信号量(Semaphore)和Mutex类似,表示可用资源的数量,和Mutex不同的是这个数量可以大于1
本节介绍的是 POSIX semaphore 库函数,详见sem_overview(7)
这种信号量不仅可用于同一进程的线程间同步,也可用于不同进程间的同步.

内存分配

C标准库里的3个分配函数
malloc 不负责把分配的内存空间清零
calloc 会负责把分配的内存空间用字节0填充
realloc 调整已分配的内存大小

POSIX标准中定义的分配函数
alloca
不是在堆上分配空间,而是在调用者函数的
栈帧上分配空间,类似于 C99 的变长数组,当调用者函数返回时自动释放栈帧
所以不需要free
这个函数不属于 C 标准库,而是在 POSIX 标准中定义的。

段错误的产生流程

  1. 用户程序要访问的一个虚拟地址,经MMU检查无权访问.
  2. MMU 产生一个异常,CPU 从用户模式切换到特权模式,跳转到内核代码中执行异常服务程序.
  3. 内核把这个异常解释为段错误,把引发异常的进程终止掉.

SRAM and DRAM

SRAM static RAM(CPU的Cache通常由SRAM组成)
DRAM dynamic RAM(内存通常由DRAM组成)
DRAM电路比SRAM简单,存储容量可以做得更大,但DRAM的访问速度比SRAM慢.

SocketPair

sockpair

线程版本

gcc socketpair.c -lpthread

进程版本

gcc socketpair.c -DFORK_VERSION

2018-03-09 16:29:04 u010859498 阅读数 51

预处理

  1. .c文件 预处理 .i文件 编译 .s文件 汇编 .o文件 链接 可执行文件
  2. 宏定义 不考虑编译器语法,单纯字符串的替换
    用于 常量 数组buffer大小 便于修改,将其定义为宏
  3. 宏函数 #define N(n) n * 10 #define ADD(a, b) (a+b) 不考虑类型语法
  4. 预处理阶段不会进行运算操作
  5. 条件编译
  6. typedef 关键字 给一个变量类型 起别名 typedef int* p 给int* 类型起别名叫p
  7. typedef unsigned long size_t
    通常给自己自定义的结构类型起别名
    typedef struct stu{
    }stu_t; typedef 与 宏 作用域不同

结构体

数组(Array),它是一组具有相同类型的数据的集合。但在实际的编程过程中,我们往往还需要一组类型不同的数据,例如对于学生信息登记表,姓名为字符串,学号为整数,年龄为整数,所在的学习小组为字符,成绩为小数,因为数据类型不同,显然不能用一个数组来存放。

在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:

struct 结构体名{
结构体所包含的变量或数组
};

结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。请看下面的一个例子:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
注意大括号后面的分号;不能少,这是一条完整的语句。
结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。

像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
结构体变量
既然结构体是一种数据类型,那么就可以用它来定义变量。例如:
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少。

stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。

你也可以在定义结构体的同时定义结构体变量:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1, stu2;

将变量放在结构体定义的最后即可。

如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名,如下所示:

struct{  //没有写 stu
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1, stu2;

这样做书写简单,但是因为没有结构体名,后面就没法用该结构体定义新的变量。

理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似,例如上面的结构体变量 stu1、stu2 的内存分布如下图所示,共占用 4+4+4+1+4 = 17 个字节。

但是在编译器的具体实现中,各个成员之间可能会存在缝隙,对于 stu1、stu2,成员变量 group 和 score 之间就存在 3 个字节的空白填充(见下图)。这样算来,stu1、stu2 其实占用了 17 + 3 = 20 个字节。

关于成员变量之间存在“裂缝”的原因,我们将在《C语言和内存》专题中的《C语言内存对齐,提高寻址效率》一节中详细讲解。
成员的获取和赋值
结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]获取单个元素,结构体使用点号.获取单个成员。获取结构体成员的一般格式为:
结构体变量名.成员名;

通过这种方式可以获取成员的值,也可以给成员赋值:

#include <stdio.h>
int main(){
    struct{
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在小组
        float score;  //成绩
    } stu1;
    //给结构体成员赋值
    stu1.name = "Tom";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;
    //读取结构体成员的值
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
    return 0;
}

运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!

除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:

struct{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };

不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。
需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储

共用体

让几个不同类型的变量 共享同一内存地址
优点:节省内存开销
缺点:同一时刻只能存储一个成员
占用空间:最大的那个成员的空间
内存对齐
1. 某一成员的偏移量必须是该成员占用大小的整数倍,如果不行,就填充它的偏移量
2. 整个结构体总的占用量是最大成员占用空间的整数倍

动态数据结构

静态链表

动态链表

2016-12-26 18:18:06 zhangke96 阅读数 2089

以下内容全部摘自UNIX环境高级编程(第3版)

用名字打开任意类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限
例如,为了打开文件/usr/include/stdio.h,需要对目录/,/usr和/usr/include具有执行权限。
对目录的读权限允许我们读目录,获得在该目录中所有文件名的列表。

创建文件时,用户ID设置为进程的有效用户ID。组ID有两种方式设置:
(1)新文件的组ID可以是进程的有效组ID
(2)新文件的组ID可以是它所在目录的组ID

函数access和faccessat

#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1

测试文件是否存在,mode为F_OK
测试读权限,mode为R_OK
测试写权限,mode为W_OK
测试执行权限,mode为X_OK
faccessat函数与access函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWDpathname参数为相对路径。否则,faccessat计算相对于打开目录(由fd参数指向)的pathname.
flag参数可以用于改变faccessat的行为,如果flag设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。

函数chmod、fchmod和fchmodat

#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
三个函数的返回值:若成功,返回0;若出错,返回-1

fchmodat函数与chmod函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWDpathname参数为相对路径。否则,fchmodat计算相对于打开目录(由fd参数指向)的pathname.
flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。
S_ISUID : 执行时设置用户ID
S_ISGID: 执行时设置组ID
S_ISVTX: 保存正文(粘着位)
S_IRWXU: 用户(所有者)读/写和执行
S_IRUSR: 用户(所有者)读
S_IWUSR: 用户(所有者)写
S_IXUSR: 用户(所有者)执行
S_IRWXG: 组读/写和执行
S_IRGRP: 组读
S_IWGRP: 组写
S_IXGRP: 组执行
S_IRWXO: 其他读/写和执行
S_IROTH: 其他读
S_IWOTH: 其他写
S_IXOTH: 其他执行

函数chown、fchown、fchownat和lchown

下面几个chown函数可用于更改文件的用户ID和组ID。如果两个参数ownergroup中的任意一个是-1,则对应ID不变。

#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);
四个函数的返回值:若成功,返回0;若出错,返回-1

在符号链接情况下,lchownfchownatflag设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向文件 的所有者。
fchownat函数与chown或者lchown函数在下面两种情况下相同的:一种是pathname参数为绝对路径,
另一种是fd参数取值为AT_FDCWDpathname参数为相对路径。在这两种情况下,如果flag参数中设置了AT_SYMLINK_NOFOLLOW标志时,fchownatlchown行为相同。
如果flag参数清除了AT_SYMLINK_NOFOLLOW标志,则fchownatchown行为相同。
如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat函数计算相对于打开目录的pathname

文件长度

stat结构成员st_size表示以字节为单位的文件的长度。此字段只对普通文件、目录文件和符号链接有意义。

FreeBSD8.0、Mac OS X 10.6.8和Solaris10对管道也定义了文件长度,它表示可从该管道中读到的字节数。

  • 对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示。
  • 对于目录,文件长度通常是一个数(如16或512)的整数倍。
  • 对于符号链接,文件长度是文件名中的实际字节数。例如下面例子中,文件长度7就是路径名usr/lib的长度:
    lrwxrwxrwx 1 root 7 Sep 25 07:14 lib -> usr/lib

文件截断

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
两个函数的返回值:若成功,返回0;若出错,返回-1

这两个函数将一个现有文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的长度就不再能访问。
如果以前长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

文件系统

任何一个叶目录(不包含任何其他目录的目录)的链接计数总是2,数值2来自于命名该目录(testdir)的目录项以及在该目录中.项。
编号为1267的i节点,其类型字段表示它是ige目录,链接计数大于或等于3.它大于或等于3的原因是,至少有3个目录项指向它:
  • 一个是命名它的目录项
  • 第二个是在该目录中的. 项
  • 第三个是在其子目录testdir中.. 项。 注意,在父目录中的每一个子目录都使该父目录的链接数增加1。
    directory

函数link、linkat、unlink、unlinkat和remove

hard link!!!!

任何一个文件都可以有多个目录项指向其i节点。创建一个指向现有文件的链接的方法是使用link函数或linkat函数
#include <unistd.h>
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1

这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已经存在,则返回出错。
只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
对于linkat函数,现有文件是通过efdexistingpath参数指定的,新的路径名是通过nfdnewpath参数指定的。
默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算。
如果两个文件描述符中的任一个设置为AT_FDCWD,那么相对应的路径名(如果它是相对路径)就通过对于当前目录进行计算。
如果任一路径名是绝对路径,相应的文件描述符参数就会被忽略。
当现有文件是符号链接时,由flag参数来控制linkat函数是创建指向现有文件符号链接的链接还是创建指向现有符号链接所指向的文件的链接。
如果在flag参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。
如果这个标志被清除了,则创建一个指向符号链接本身的链接。

为了删除一个现有的目录项,可以调用unlink函数

#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1

这两个函数删除目录项,并将由pathname所引用文件的链接计数减1。如果对该文件还有其他链接,
则仍可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。
为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。
如果对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:
* 拥有该文件;
* 拥有该目录;
* 具有超级用户权限。
只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容——只要有进程打开了该文件,其内容也不能删除。
关闭一个文件时,内核首先检查打开文件的进程个数;如果这个计数达到0,内核再去检查其链接计数;
如果计数也是0,那么就删除该文件的内容。
如果pathname参数是相对路径名,那么unlinkat函数计算相对于由fd文件描述符参数代表的路径名。
如果fd参数设置为AT_FDCWD,那么通过相对于调用进程的当前工作目录来计算路径名。
如果pathname参数是绝对路径名,那么fd参数被忽略。
flag参数给出了一种方法,是调用进程可以改变unlinkat 函数的默认行为。
AT_REMOVEDIR标志被设置时,unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除,unlinkatunlink执行同样的操作。

unlink的特性经常被程序用来确保即使在程序崩溃时,他所创建的临时文件也不会遗留下来。

进程用opencreat创建一个文件,然后立即调用unlink,因为该文件仍旧是打开的,所以不会将其内容删除。

只有当进程关闭该文件或终止时(在这种情况下,内核关闭该进程所打开的全部文件),该文件的内容才被删除。

如果pathname是符号链接,那么unlink删除符号链接,而不是删除由该链接所引用的文件。
给出符号链接名的情况下,没有一个函数能删除由该链接所引用的文件。

remove函数

我们也可以用remove函数解除对一个文件或者目录的链接。对于文件,remove的功能与unlink相同。
对于目录,remove的功能与rmdir相同。

#include <stdio.h>
int remove(const char* pathname);
返回值:若成功,返回0;若出错,返回-1

函数renamerenameat

文件或目录可以用rename函数或者renameat函数进行重命名。

#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *new name);
两个函数的返回值:若成功,返回0;若出粗,返回-1;

根据oldname是指文件、目录还是符号链接,有几种情况需要加以说明。我们也必须说明如果newname已经存在时将会发生什么。

  1. 如果oldname指的是一个文件而不是目录,那么为该文件或符号链接重命名。
    在这种情况下,如果newname已存在,则它不能引用一个目录。
    如果newname已存在,而且不是一个目录,则先将该目录项删除然后将oldname重命名为newname
    对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。
  2. 如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,
    而且该目录应当是空目录(空目录指的是该目录中只有. 和 .. 项)。如果newname存在(而且是一个空目录),
    则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。
    例如,不能将/usr/foo重命名为重命名为/usr/foo/testdir,因为旧名字(/usr/foo)是新名字的路径前缀,因而不能将其删除。
  3. 如若oldnamenewname引用符号链接,则处理的是符号链接本身,而不是他所引用的文件。
  4. 不能对. 和 .. 重命名。更确切地说,. 和 .. 都不能出现在oldnamenewname的最后部分。
  5. 作为一个特例,如果oldnamenewname引用同一个文件,则函数不做任何更改而成功返回。

如果newname已经存在,则调用进程对它需要有写权限(如同删除情况一样)。另外,调用进程将删除oldname目录项,
并可能要创建newname目录项,素以它需要对包含oldname及包含newname的目录具有写和执行权限。
除了当oldnamenewname指向相对路径名时,其他情况下renameat函数与rename函数功能相同。
如果oldname参数指定了相对路径,就相对于oldfd参数引用的目录来计算oldname
类似的,如果newname指定了相对路径,就像对于newfd引用的目录来计算newname
oldfdnewfd参数(或两者)都能设置为AT_FDCWD,此时相对于当前目录来计算相应的路径名。

符号链接

符号链接是对一个文件的间接指针,它与上一节所述的硬链接有所不同, 硬链接直接指向文件的i结点。

引入符号链接的原因是为了避开硬链接的一些限制。
* 硬链接通常要求链接和文件位于同一个文件系统中。
* 只有超级用户才能创建指向目录的硬链接(在底层文件系统支持的情况下)。

对符号链接以及指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。
符号链接一般用于将一个文件或整个目录结构一道系统中另一个位置。

创建和读取符号链接

可以用symlinksymlinkat函数创建一个符号链接。
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
两个函数的返回值:若成功,返回0;若出错,返回-1

函数创建了一个指向actualpath的新目录项sympath。在创建此符号链接时,并不要求actualpath已经存在。
并且,actualpathsympath并不需要位于同一个文件系统中。
symlinkat函数与symlink函数类似,但symlink参数根据相对于打开文件描述符引用的目录(由fd参数指定)进行计算。
如果sympath参数指定的是绝对路径或者fd参数设置了AT_FDCWD值,那么symlinkat就等同于symlink函数。


因为open函数跟随符号链接,所以需要有一种方法打开该链接本身,并读该链接中的名字。
readlinkreadlinkat函数提供了这种功能。

#include <unistd.h>
ssize_t readlink(const char *restrict pathname, char *restrict buf, size_t bufsize);
ssize_t readlinkat(int fd, const char *restrict pathname, char *restrict buf, size_t buf);
两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1

两个函数组合了openreadclose的所有操作。如果函数成功执行,则返回读入buf的字节数。
buf中返回的符号链接的内容不以null字节终止。
pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWDreadlinkat函数的行为与readlink相同。
但是,如果fd参数是一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于有fd代表的打开目录的路径名。

文件的时间

字段    说明     例子     ls(1)选项

st_atim   文件数据的最后访问时间    read    -u
st_mtim   文件数据的最后修改时间    write    默认
st_ctim    i节点状态的最后修改时间    chmodchown    -c
a 访问
m(modify) 修改
c i节点修改
fileTime

函数futimensutimensatutimes

一个文件的访问和修改时间可以用以下几个函数更改。futimensutimensat函数可以指定纳秒级精度的时间戳。
用到的数据结构是与stat函数族相同的timespec结构。
#include <sys/stat.h>
#include <fcntl.h> /* Definition of AT_* constants */
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
两个函数返回值:若成功,返回0;若出错,返回-1
struct timespec {
    time_t tv_sec;         /* seconds */
    long    tv_nsec;       /* nanoseconds */
};

这两个函数的times数组参数的第一个元素包含访问时间,第二个元素包含修改时间。这两个时间值是日历时间,
如1.10节所述,这是自特定时间(1970年1月1日00:00:00)以来所经过的秒数。不足秒的部分用纳秒表示。
时间戳可以按下列4种方式之一进行指定。
1. 如果times参数是一个空指针,则访问时间和修改时间两者都设定为当前时间。
2. 如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_NOW
相应的时间戳就设置为当前时间,忽略相应的tv_sec字段。
3. 如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_OMIT
相应的时间戳保持不变,忽略相应的tv_sec字段。
4. 如果times参数指向两个timespec结构的数组,且tv_nsec字段的值为既不是UTIME_NOW也不是UTIME_OMIT
在这种情况下,相应的时间戳设置为相应的tv_sectv_nsec字段的值。

执行这些函数所要求的优先级取决于times参数的值
* 如果times是一个空指针,或者任一tv_nsec字段设为UTIME_NOW,则进程的有效用户ID必须等于该文件的所有者ID
进程对该文件必须具有写权限或者进程是一个超级用户进程。
* 如果times是非空指针,并且任一tv_nsec字段的值既不是UTIME_NOW也不是UTIME_OMIT
则进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件只具有写权限是不够的。
* 如果times是非空指针,并且两个tv_nsec字段的值都为UTIME_OMIT,就不执行任何的权限检查。

futimens函数需要打开文件来更改它的时间,utimensat函数提供了一种使用文件名更改文件时间的方法。
pathname参数是相对于fd参数进行计算的,fd要么是打开目录的文件描述符,要么设置为特殊值AT_FDCWD(强制
通过相对于调用进程的当前目录计算pathname)。如果pathname指定了绝对路径,那么fd参数被忽略。
utimensatflag参数可用于进一步修改默认行为。如果设置了AT_ SYMLINK_NOFOLLOW标志,
则符号链接本身的时间就会被修改(如果路径名指向符号链接)。默认的行为是跟随符号链接,并修改符号链接指向的文件的时间。

utimes函数

#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
函数返回值:若成功,返回0;若出错,返回-1

utimes函数对路径名进行操作。times参数是指向包含两个时间戳(访问时间和修改时间)元素的数组的指针,两个时间戳使用秒和微妙表示的。

struct timeval {
            time_t    tv_sec;    /* seconds */
            long       tv_usec;    /* microseconds */
};

注意,我们不能对状态更改时间st_ctim(i节点最近被修改时间)指定一个值,因为调用utimes函数时,此字段会被自动更新。

函数mkdirmkdiratrmdir

mkdirmkdirat函数创建目录,用rmdir函数删除目录。

#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
两个函数返回值,若成功,返回0;若出错,返回-1

这两个函数创建一个新的空目录。其中, . 和 .. 目录项是自动创建的。所指定的文件访问权限mode
由进程的文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的mode(只指定读、写权限)。但是,对于目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名。
mkdirat函数与mkdir函数类似。当fd参数具有特殊值AT_FDCWD或者pathname参数指定了绝对路径名时,
mkdiratmkdir完全一样。否则,fd参数是一个打开目录,相对路径名根据此打开目录进行计算。
rmdir函数可以删除一个空目录。空目录是只包含 . 和 .. 这两项的目录。

#include <unistd.h>
int rmdir(const char *pathname);
返回值:若成功,返回0;若出错,返回-1

读目录

#include <dirent.h>
DIR *opendir (const char *pathname);
DIR *fdopendir (int fd);
两个函数返回值:若成功,返回指针;若出错,返回NULL
struct dirent *readdir(DIR *dp);
返回值:若成功,返回指针;若在目录尾或出错,返回NULL
void rewinddir (DIR *dp);                        /* reset directory stream */
int closedir (DIR *dp);
返回值:若成功,返回0;若出错,返回-1
long telldir (DIR *dp);                               /* return current location in directory stream */
返回值:与dp关联的目录中的当前位置
void seekdir (DIR *dp, long loc);

定义在头文件<dirent.h>中的dirent结构与实现有关。实现对此结构所做的定义至少包含下列两个成员:

ino_t d_ino;                /* i-node number */
char  d_name[];         /* null-terminated filename */

函数chdirfchdirgetcwd

进程调用chdirfchdir函数可以更改当前工作目录

#include <unistd.h>
int chdir (const char *pathname);
int fchdir (int fd);
两个函数的返回值:若成功,返回0;若出错,返回-1

用函数getcwd得到当前工作目录完整的绝对路径名

#include <unistd.h>
char *getcwd(char *buf, size_t size);
返回值:若成功,返回buf,若出错,返回NULL

必须向此函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度size(以字节为单位)。
该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止NULL字符,否则返回出错。

c 编程-linux

阅读数 326

linux c 编程

阅读数 432

Linux 下 C 编程

阅读数 21

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