抱歉了, 现在只能做成这样, 因为这是我在vim里面写的笔记再整理的笔记了, 有点随笔, 但是干货还是有点, 看的挺慢的, 借鉴了很多人的知识, 有些不好写也就没有写, 也怕写错, 毕竟自己也才开始. 本来用ATOM写的, 但是, 这玩意居然安不了插件, 无奈这几天才用vim写的. 刚开始看的人估计都跟我一样, 很多不懂, 所以我也查了各种资料, 所以写的很浅显. 以后写的好了再来补充, 我继续整改我的配置了
主要知识
涉及的函数:
在 ls.cpp 代码部分, 文件类型;
start();
DIR 数据类型. opendir(), readdir(), closedir();
在 va_list.cpp 代码部分:
va_list(), va_start(), va_arg(), va_end();
在 getID.cpp 代码部分, 获得ID;
getpid(), getuid(), getgid();
在 process_control.cpp 代码里面, 进程;
fork(), waitpid();
/* ************************************************************************
> File Name: ls.cpp
> Author: Function_Dou
> Created Time: 2018年01月18日 星期四 11时00分55秒
> 第一次修改 : 增加了err_sys, err_quit的注释, 来源. 18.1.18/ 13:11
> agrc : 代表传入的参数的个数
> agrv[] : 代表传入的变量, agrv[0]代表文件名
> 函数 DIR *opendir(const char *pathname),即打开文件目录返,回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:
struct dirent *readdir(DIR *dp);
DIR *opendir(const char *pathname); // 第一步, 打开文件, 执行初始化操作
void rewinddir(DIR *dp); // 返回目录中的第一个目录项
int closedir(DIR *dp); // 最后一步, 关闭文件
long telldir(DIR *dp);
void seekdir(DIR *dp,long loc);
dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件
> dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,如果想获得类似ls -l那种效果的文件信息,必须要靠stat函数了
eaddir函数读取到的文件名存储在结构体dirent的d_name成员中,而函数
int stat(const char *file_name, struct stat *buf);
struct stat
{
mode_t st_mode; //文件访问权限
ino_t st_ino; //索引节点号
dev_t st_dev; //文件使用的设备号
dev_t st_rdev; //设备文件的设备号
nlink_t st_nlink; //文件的硬连接数
uid_t st_uid; //所有者用户识别号
gid_t st_gid; //组识别号
off_t st_size; //以字节为单位的文件容量
time_t st_atime; //最后一次访问该文件的时间
time_t st_mtime; //最后一次修改该文件的时间
time_t st_ctime; //最后一次改变该文件状态的时间
blksize_t st_blksize; //包含该文件的磁盘块的大小
blkcnt_t st_blocks; //该文件所占的磁盘块
};
**************************************************************/
#include "apue.h"
#include <dirent.h>
int main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;
// err_sys, err_quit : 是apue.h里面的自定义的, 原型是用va_list(), va_start(), va_end()写的, 了解可看文件va_list.cpp
if (argc != 2)
err_quit("usage: ls directory_name");
if ((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
while((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);
closedir(dp);
exit(0);
}
/***********************************************************************
> File Name: va_list.cpp
> Author: Function_Dou
> Created Time: 2018年01月18日 星期四 12时44分31秒
VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活
VA_LIST的用法:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。
****************************************************************
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
目的是将求出n对齐后的字节数,从这里来看应该是按照4字节的整数倍来对齐
va_list ap ; 定义一个va_list变量ap
va_start(ap,v) ;执行ap = (va_list)&v + _INTSIZEOF(v),ap指向参数v之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
va_arg(ap,t) , ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出当前ap指针所指的值,并使ap指向下一个参数。
实现将ap指向的参数取出赋给t,并且让p指向下一个可变参数 具体是先(ap += _INTSIZEOF(t)) 之后用 (*( t *)(ap-_INTSIZEOF(t))),设计的也很巧.
va_end(ap) ; 清空va_list ap。
#define va_end(ap) ( ap = (va_list)0 )
最后将ap指针设置为空。
typedef char * va_list; va_list 就是char *类型的指针。
************************************************************************/
// 打印字符串
#include <stdarg.h>
#include <stdio.h>
#include "apue.h"
void Function(char *str, ...)
{
char *str1;
str1 = str;
va_list v1;
va_start(v1, str);
while(str1)
{
printf("%s\t", str1);
str1 = va_arg(v1, char *);
}
va_end(v1);
}
int main()
{
Function ("one", "two", "three", NULL);
exit(0);
}
/*
#include <stdio.h>
#include <stdarg.h>
#include "apue.h"
void ar_cnt(int cnt,...);
void ar_cst(char const *s,...);
int main()
{
//int in_size = _INTSIZEOF(int);
int in_size = 4;
printf("int_size=%d\n",in_size);
ar_cnt(5,1,2,3,4);
return 0;
}
void ar_cnt(int cnt,...)
{
int value1=0;
int i=0;
va_list arg_ptr;
va_start(arg_ptr,cnt);
for(i=0;i<cnt;i++)
{
value1=va_arg(arg_ptr,int);
printf("posation %d=%d\n",value1,i+1);
}
va_end(arg_ptr);
}
/*************************************************************************
> File Name: getID.cpp
> Author: Function_Dou
> Created Time: 2018年01月18日 星期四 13时12分59秒
************************************************************************/
#include <stdio.h>
//#include <iostream>
#include <unistd.h>
#include "apue.h"
//using namespace std;
int main()
{
// getpid() : 在头文件unistd.h文件里面
// 取得进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题
printf("process ID %ld uid = %d. gid = %d\n", (long)getpid(), getuid(), getgid());
return 0;
}
/*
样列
process ID 3864 uid = 0. gid = 0
process ID 3999 uid = 0. gid = 0
*/
/*************************************************************************
> File Name: process_control.cpp
> Author: Function_Dou
> Created Time: 2018年01月18日 星期四 13时35分10秒
************************************************************************/
/************************************************************************
fork() : 通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
> fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
execlp() : 从PATH 环境变量中查找文件并执行
execlp() 会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中
execlp() : 要求参数必须以NULL结尾, 而不是以'\n'结尾. 一般采用 execlp($1, $2, (char *)0)进行将末尾设置为 NULL;
fork() : 创建一个新进程, 一般在函数中创建是为子进程;
调用execlp() 以执行从标准输入读入指令, fork()跟execlp()就是操作系产生新进程;
waitpid() : 指令目前进程要等待的进程. waitpid()返回等待的子进程的终止状态(ststus);
waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数status 可以设成NULL. 参数pid 为欲等待的子进程识别码
与进程相同, 线程也用ID标识. 但是线程ID只在所属的进程内其作用, 在其他进程就没有意义了, 即, 线程属于进程;
*************************************************************************/
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include "apue.h"
int main()
{
char buf[MAXLINE];
pid_t pid;
int status;
printf("%%");
while(fgets(buf, MAXLINE, stdin) != NULL)
{
if(buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = 0;
if((pid = fork()) < 0)
err_sys("fork error");
else if(pid == 0)
{
execlp(buf, buf, (char *)0);
err_ret("couldn't execute: %s", buf);
exit(127);
}
if((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%%");
}
return 0;
}
/*****************fork() 的一个示例****************************
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count=0;
fpid=fork();
if (fpid < 0)
printf("error in fork!");
else if (fpid == 0)
{
printf("i am the child process, my process id is %d\n",getpid());
printf("我是爹的儿子\n");
count++;
}
else
{
printf("i am the parent process, my process id is %d\n",getpid());
printf("我是孩子他爹\n");
count++;
}
printf("统计结果是: %d\n",count);
return 0;
}
*/
stat和fstat函数返回包含所有文件属性的一个信息结构;
UNIX文件系统大多数实现并不在目录项中存放属性,因为当一个文件链接多个硬链接时,很难保证多个副本之间的属性同步;
示例在process_control.cpp
execlp() : 要求参数必须以NULL结尾, 而不是以’\n’结尾. 一般采用 execlp(
1,
2, (char *)0)进行将末尾设置为 NULL;
fork() : 创建一个新进程, 一般在函数中创建是为子进程;
调用execlp() 以执行从标准输入读入指令, fork()跟execlp()就是操作系产生新进程;
waitpid() : 指令目前进程要等待的进程. waitpid()返回等待的子进程的终止状态(ststus);
与进程相同, 线程也用ID标识. 但是线程ID只在所属的进程内其作用, 在其他进程就没有意义了, 即, 线程属于进程;
perror() : 将上一个函数发生错误的原因输出到标准设备(stdin), 错误原因依照error的值来决定将要输出的字符串;
时间值:
- 时钟时间 : 又称墙上时间, 它是进程运行的总时间量, 其值与同时运行的总进程数有关
- 用户CPU时间 : 执行用户命令所用时间量
- 系统CPU时间 : 进程执行内核所用的时间
参考网址 : http://blog.csdn.net/sgbfblog/article/details/7772153
sbrk() : 按指定的字节分配内存.sbrk(1) 每次让堆往栈的方向增加 1 个字节的大小空间;
brk() 和 sbrk() 改变 “program brek” 的位置,这个位置定义了进程数据段的终止处(也就是说,program break 是在未初始化数据段终止处后的第一个位置);
缓存
应用缓存是一次性读入大量的数据进行缓冲, 这样之后不是读操作都要调用read, 而是可以直接在缓冲区读数据
管理员模式和用户模式之间的切换需要消耗时间,相比之下,磁盘的I/O操作消耗的时间更多,为了提高效率,内核也使用缓冲技术来提高对磁盘的访问速度。
程序读取数据的时候只能通过系统的read进行读
read把数据从内核缓冲区复制到进程缓冲区中, write把数据从进程缓冲区复制到内核缓冲区中.但他们并不代表数据在内核和磁盘之间的交换
应用内核缓冲区技术, 提高磁盘的I/O效率, 优化磁盘的读写操作. 当写入磁盘时调用write之后, 会调用ffulsh将数据写入磁盘, 以防止数据的意外丢失.
内核可以在任何时候写磁盘,但并不是所有的write操作都会导致内核的写动作。内核会把要写的数据析时存在缓冲区中.积累到一定数量后再一次写人。有时会导致意外情况,比如突然断电,内核还来不及把内核缓冲区中的数据写到磁盘上,这些更新的数据就会丢失。
从理论上讲,内核可以在任何时候写磁盘,但并不是所有的write操作都会导致内核的写动作。内核会把要写的数据析时存在缓冲区中.积累到一定数量后再一次写人。有时会导致意外情况,比如突然断电,内核还来不及把内核缓冲区中的数据写到磁盘上,这些更新的数据就会丢失。
不带缓冲指每个read, write都调用内核的一个系统调用
不同缓冲长度对read和write的影响
文件描述符
对于内核, 每次打开文件都将通过文件描述符引用. 文件描述符是一个非负整数. 当打开或创建一个文件时, 内核都会向进程返回一个文件描述符. 返回的文件描述符相当于告诉进程, 它能对文件做什么操作.
文件的符号STDIN_FILENO(0), STDOUT_FILENO(1), STDERR_FILENO(2).
open(const char* path, …)与openat(int fd,const cahr* path, …)
1. path参数指定的是绝对路径时, fd可以忽略, open就相当与openat
2. path参数指定的是相对路径时, fd参数指出了相对路径名在系统中的开始地址, fd参数是通过打开相对路径名所在的目录获得的
3. path参数指定了相对路径名, fd参数具有特殊值AT_FDCWD. 这种情况下, 路径名在当前工作目录中获取, openat与open相当
openat函数解决了:
1. 让线程可以使用相对路径名打开目录中的文件, 而不是只能打开当前的工作目录
2. 可以避免time-of-check-to-time-of-use(TOCTTOU).
TOCTTOU : 如果有两个基于文件的函数调用, 第二个文件调用的是第一个文件的结果, 那么第二个文件是脆弱的.
close();
关闭一个文件时还会释放进程加在这个文件上所有的记录锁.
当一进程结束的时候, 内核也会自动关闭它所有的打开的文件. 所以, close()可以不显示的调用.
记录锁 : 当一个进程正在读取,修改文件的某一区域时, 它可以阻止其他进程修改同一区域.
相关记录, 例子可以看相关博客 : http://blog.csdn.net/wudizuijimo/article/details/5257217
lseek();
指定文件的偏移量.
偏移量可能会造成空洞, 空洞不消耗磁盘空间.也可能比统一内容的文件更加节省磁盘空间.
空洞用处
- 下载时如果没有空洞文件,多线程下载时文件就只能从一个地方写入,这就不能发挥多线程的作用了.如果有了空洞文件,可以从不同的地址写入,就完成了多线程的优势.
- 开发过程中有时候需要为某个文件迅速地分配固定大小的磁盘空间
a. 可以让文件尽可能的占用连续的磁盘扇区,减少后续写入和读取文件时的磁盘寻道开销;
b. 迅速占用磁盘空间,防止使用过程中所需要空间不足;
c. 后面追加数据的话,不会需要改变文件大小,所以后面将部设计metadata的修改.
I/O效率:
大多数文件系统为改善性能都采用预读技术.
预读技术:
当检测到正在进行顺序读取时, 系统就试图读取比应用要求更多的数据.预读的效果在超过一定的数据长度基本不改变, 所以时间与每次缓存的长度关系就不大了.
第一运行文件时, 系统会大量进行缓存, 所以之后的读取就会很快速, 第一会可能的慢一点