前言
Linux C语言2020大二上半学期总结,整理到吐血
内存管理
- 整数页为4096 = 4K
-
进程和程序
- 程序 - 硬盘上的可执行文件
- 进程 - 在内存中运行的程序
-
进程中的内存区域划分
- –代码区-来存放可执行文件的操作指令-不可写的
- –只读常量区- 存放字符串常量,以及const修饰的全局变量
- –全局区/数据区 - 存放已经初始化的全局变量和static修饰的局部变量
- –BSS段- 存放没有初始化的全局变量和静态局部变量,该区域会在main函数执行之前进行自动清零
- –堆区 - 使用malloc/calloc/realloc/free函数处理的内存,该区域的内存需要程序员手动申请和手动释放
- –栈区 - 存放局部变量、形参、const修饰的局部变量,以及块变量,该区域的内存由操作系统负责分配和回收,程序员尽管放心使用即可
-
总结
-
–按照地址从小到大进行排列,进程中的内存区域依次为:代码区、只读常量区、全局区/数据区、BSS段、 堆区、栈区
-
–其中代码区和只读常量区一般统称为代码区;其中全局区/数据区和BSS段一般统称为全局区/数据区
-
–栈区和堆区之间并没有严格的分割线,可以进行微调,并且堆区的分配一般按照地址从小到大进行,而栈区的分配一般按照地址从大到小进行分配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t53oGpVP-1615732028953)(http://img.wangzun233.top/uc00.png)]
-
地址分类
•物理地址:
也就是内存单元的实际地址,用于芯片级内存单元寻址。 物理地址也由32位无符号整数表示。
•逻辑地址:
程序代码经过编译后出现在 汇编程序中地址。每个逻辑地址都由一个段和偏移量组成。
•线性地址(虚拟地址):
在32位CPU架构下,可以表示4G的地址空间,用16进制表示就是0x00000000—0Xffff ffff
寄存器
•16位处理器:即80386之前的系列,一般以8086为代表,8086 CPU 中寄存器总共为 14 个,且均为 16 位 。
•32位处理器:以80386为代表,除了段寄存器位数仍为16位之外,其他的寄 存器都为32位,同时新增FS,GS两个段寄存器,即:
–4个数据寄存器(EAX、EBX、ECX和EDX)
–2个变址和指针寄存器(ESI和EDI)
–2个指针寄存器(ESP和EBP)
–6个段寄存器(ES、CS、SS、DS、FS和GS)
–1个指令指针寄存器(EIP)
–1个标志寄存器(EFlags)
ALU
•算术逻辑单元 (Arithmetic Logic Unit, ALU)是中央处理器(CPU)的执行单元,是所有中央处理器的核心组成部分,基本操作包括加、减、乘、除四则运算,与、或、非、异或等逻辑操作,以及移位、比较和传送等操作.我们通常说的一个CPU是16位或者是32位,指的是ALU 的宽度,即字长,它是CPU在同一时间内能处理的二进制的位数。字长反映了CPU的计算精度。
三大总线
•数据总线DB:
用于传送数据信息。数据总线是双向三态形式的总线,即他既可以把CPU的数据传送到存储器或I/O接口等其它部件,也可以将其它部件的数据传送到CPU。
•地址总线AB:
是专门用来传送地址的,由于地址只能从CPU传向外部存储器或I/O端口,所以地址总线总是单向三态的,这与数据总线不同。地址总线的位数决定了CPU可直接寻址的内存空间大小。一般来说,若地址总线为n位,则可寻址空间为2^n字节。
•控制总线CB:
用来传送控制信号和时序信号。控制信号中,有的是微处理器送往存储器和I/O接口电路的,也有是其它部件反馈给CPU的,因此,控制总线的传送方向由具体控制信号而定,一般是双向的,控制总线的位数要根据系统的实际控制需要而定。
虚拟内存
•每个进程的用户空间都是完全独立、互不相干的。不信的话,你可以把上面的程序同时运行10次(当然为了同时运行,让它们在返回前一同睡眠100秒吧),你会看到10个进程占用的线性地址一模一样。
•本质上说,虚拟内存地址剥夺了应用程序自由访问物理内存地址的权利。进程对物理内存的访问,必须经过操作系统的审查。只要操作系统把两个进程的进程空间对应到不同的内存区域,就让两个进程空间成为“老死不相往来”的两个小王国。两个进程就不可能相互篡改对方的数据,进程出错的可能性就大为减少。
•另一方面,有了虚拟内存地址,内存共享也变得简单。操作系统可以把同一物理内存区域对应到多个进程空间。共享库也是类似。对于任何一个共享库,计算机只需要往物理内存中加载一次,就可以通过操纵对应关系,来让多个进程共同使用
地址转换
•逻辑地址经段机制转化成线性地址;线性地址又经过页机制转化为物理地址。
•在 8086 的实模式下, 通过(段基址:段偏移量)计算出内存单元的物理地址,在IA32的保护模式下,这个逻辑地址不是被直接送到内存总线而是被送到内存管理单元(MMU)。MMU由一 个或一组芯片组成, 其功能是把逻辑地址映射为物理地址,即进行地址转换,如图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWOzkg89-1615732028957)(http://img.wangzun233.top/uc01.png)]
实模式(16位处理器寻址)
•早期8088CPU时期.当时由于CPU的性能有限,一共只有20位地址线(地址空间只有1MB),但是一个尴尬的问题出现了,ALU的宽度只有16位,也就是说ALU不能计算20位的地址。为了解决这个问题,分段机制被引入.为了构成20位的主存地址,8088处理器设置了4个段寄存器以及8个通用寄存器,每个寄存器都是16位的,同时访问内存的指令中的地址也是16位的,当某个指令想要访问某个内存地址时,它通常需要用(段基址:段偏移量)这种格式来表示。这样就形成一个20位的实际地址,也就实现了从16位内存地址到20位实际地址的转换,或者叫“映射”。
malloc 函数
char*a=NULL;
a=(char*)malloc(100*sizeof(char));
free(a);
malloc函数详解
•使用malloc函数申请内存时,除了申请所需要的内存大小之外,可能还会申请额外的12个字节,用于存储一些管理内存的相关信息,比如内存的大小等等。使用malloc申请的内存,一定要注意不要对所申请的内存空间进行越界访问,避免造成数据结构的破坏。
•一般来说,使用malloc申请比较小的动态内存时,操作系统会一次性分配33个内存页的大小,最终的目的就是为了提高效率而已。
•可以使用命令cat /proc/<pid>/maps
查看某个进程占用的内存区域。 (pid是进程号,proc下的各个进程目录占磁盘大小都是0,因为其数据都存在于内存,该文件只是一个映射,并且maps文件中的内存地址为已经映射了物理内存的虚拟内存地址)
•每行数据格式如下:
•(内存区域)开始-结束 访问权限 偏移 主设备号:次设备号 i节点 文件。
注意,你一定会发现进程空间只包含三个内存区域,似乎没有上面所提到的堆、bss等,其实并非如此,程序内存段和进程地址空间中的内存区域是种模糊对应,也就是说,堆、bss、数据段(初始化过的)都在进程空间中由数据段内存区域表示
free详解
•使用free函数释放动态内存 一般来说,使用malloc申请比较大的的内存时,系统会分配34个内存页,当所申请的内存超过34个内存页时,系统会再次分配33个内存页(也就是按照33个内存页为基本单位分配) 而对于使用free释放内存时,则释放多少就减少多少,当使用free释放完毕所有内存时,系统可能会保留33个内存页以备再次申请使用,以此提高效率。
获取进程ID号
- •./可运行文件
&
Ps –aux
- 在程序中加入
#include <unistd.h> getpid()
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = getpid();//获取当前程序的进程号
pid_t ppid = getppid();//获取当前进程的父进程
printf("当前进程ID%d,父进程ID%d\n",pid,ppid);
while(1);
return 0;
}
//ps -ef
size命令
•使用命令size查看程序的内存分配情况:
• size a.out
•text(代码区) data(数据区) bss(BSS段) dec(十进制的总和) hex(十六进制的总和) filename(文件名)
文件
•在Unix/linux系统中,几乎所有的一切都可以看作文件,因此,对于文件的操作适用于各种输入输出设备等等,当然目录也可以看作文件。一切皆文件。
•开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源•在Unix/linux系统中,几乎所有的一切都可以看作文件,因此,对于文件的操作适用于各种输入输出设备等等,当然目录也可以看作文件。一切皆文件。
•开发者仅需要使用一套 API 和开发工具即可调取 Linux 系统中绝大部分的资源
文件的分类
-
普通文件:Linux中最多的一种文件类型, 包括纯文本文件、二进制文件(binary);数据格式的文件(data);各种压缩文件.第一个属性为 [-]
-
目录文件就是目录, 能用 # cd 命令进入的。第一个属性为 [d],例如 [drwxrwxrwx]
-
块设备文件 : 就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。第一个属性为 [b]
-
字符设备文件:即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]
-
套接字文件这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。第一个属性为 [s],最常在 /var/run目录中看到这种文件类型
-
管道文件FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p]
-
链接文件类似Windows下面的快捷方式。第一个属性为 [l],例如 [lrwxrwxrwx]
文件操作函数
fopen()/fclose()/fread()/fwrite()/fseek()
文件描述符
–文件描述符是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有执行I/O操作的系统调用都通过文件描述符;文件描述符是一个简单的非负整数,用以表明每个被进程打开的文件
-
内核缺省为每个进程打开三个文件描述符:
-
stdin,标准输入,默认设备是键盘,文件编号为0
-
stdout,标准输出,默认设备是显示器,文件编号为1,也可以重定向到文件
-
stderr,标准错误,默认设备是显示器,文件编号为2,也可以重定向到文件
-
ll /proc/11990/fd – 查看所有文件打开的文件描述符
open函数
–#include <sys/types.h>
–#include <sys/stat.h>
–#include <fcntl.h>
–int open(const char *pathname, int flags);
–int open(const char *pathname, int flags, mode_t mode);
–int creat(const char *pathname, mode_t mode);
-
函数功能:主要用于打开/创建 一个 文件/设备
-
返回值:成功返回新的文件描述符,失败返回-1 描述符就是一个小的非负整数,用于表示当前文件
- 第一个参数:字符串形式的文件路径和文件名
- 第二个参数:操作标志 必须包含以下访问模式中的一种:
- O_RDONLY - 只读
- O_WRONLY - 只写
- O_RDWR - 可读可写
- O_APPEND - 追加,写入到文件的尾部
- O_CREAT - 文件不存在则创建,存在则打开 O_EXCL - 与O_CREAT搭配使用,存在则open失败 O_TRUNC - 文件存在且允许写,则清空文件
- 第三个参数:操作模式,权限 当创建新文件时,需要指定的文件权限, 如: 0644
creat函数
–#include <sys/types.h>
–#include <sys/stat.h>
–#include <fcntl.h>
–int creat(const char *pathname, mode_t mode);
- 函数功能:用于创建文件,存在则更新,不存在则创建
- 参数:第一个参数路径
- 第二个参数权限:成功返回文件描述符,失败返回-1。
- creat函数是通过调用open实现的
close函数
–#include <unistd.h>
–int close(int fd);
- 函数功能:主要用于关闭参数fd指定的文件描述符,也就是让描述符fd不再关联任何一个文件,以便于下次使用
read函数
–#include <unistd.h>
–ssize_t read(int fd, void *buf, size_t count);
- 第一个参数:文件描述符(从哪里读)
- 第二个参数:缓冲区的首地址(存到哪里去)
- 第三个参数:读取的数据大小
- 返回值:成功返回读取到的字节数,返回0表示读到文件尾失败返回-1
- 函数功能:表示从指定的文件中读取指定大小的数据
write函数
–#include <unistd.h>
– ssize_t write(int fd,const void *buf,size_t count);
- 第一个参数:文件描述符(写入到哪里去)
- 第二个参数:缓冲区的首地址(数据从哪里来)
- 第三个参数:写入的数据大小
-
返回值:成功返回写入的数据大小,0表示没有写入, 失败返回-1
-
函数功能:表示将指定的数据写入到指定的文件中
-
注意:read和write函数一般默认以二进制形式进行读写操作
lseek函数
–#include <sys/types.h> #include <unistd.h>
–off_t lseek(int fd,off_t offset,int whence);
- 第一个参数:文件描述符(表示在哪个文件中操作)
- 第二个参数:偏移量(正数表示向后,负数向前)
- 第三个参数:起始位置(从什么地方开始偏移) SEEK_SET - 文件开头位置 SEEK_CUR - 文件当前位置 SEEK_END - 文件结尾位置
-
返回值:成功返回距离文件开头位置的偏移量, 失败返回-1
-
函数功能:主要用于调整文件的读写位置
access函数
•#include <unistd.h>
•int access ( const char* pathname, // 文件路径 int mode // 访问模式 );
-
函数功能:按实际用户ID和实际组ID(而非有效用户ID和有效组ID),进行访问模式测试。
参数:
- 路径
- mode取R_OK/W_OK/X_OK的位或, 测试调用进程对该文件, 是否可读/可写/可执行, 或者取F_OK,测试该文件是否存在。
-
返回值:成功返回0,失败返回-1。
dup函数
–#include <unistd.h>
–int dup(int oldfd);
-
功能:复制一个文件描述符
-
参数:oldfd:源描述符
-
返回值: 错误返回-1,errno被设置为相应的错误码成功返回新的文件描述符
dup2函数
–int dup2(int oldfd, int newfd);
- 功能:复制一个文件描述符
- 参数:oldfd:指定源描述符
- newfd:指定新的描述符 如果这个描述符原来是打开的,使用之前先关闭.
- 返回值: 错误返回-1errno被设置为相应的错误码成功返回新的文件描述符
获取文件元数据
-
文件有两部分构成 文件的内容 文件的属性
-
文件的元数据就是文件的属性
-
每个文件都有一个对应的i节点,这个I节点里面保存了文件的元数据和所在的硬盘位置。中文译名为"索引节点"。
-
系统打开文件这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block。
命令
–ls –i
查看I节点
–inode编号
文件的类型 文件的权限 硬链接数 属主 属组 大小 最后修改时间
stat 文件名
查看文件的元数据
stat函数
–#include <sys/types.h>
–#include <sys/stat.h>
–#include <unistd.h>
–int stat(const char *pathname, struct stat *buf);
功能:获取文件的身份信息
-
参数:
pathname:
指定了文件的名字 -
buf:
将文件的身份信息存储到buf指定的空间里 -
返回值:成功 0错误 -1 errno被设置为相应错误码
fstat函数
–#include <sys/types.h>
–#include <sys/stat.h>
–#include <unistd.h>
–int fstat (int fd, struct stat* buf);
-
功能:获取文件的身份信息
-
参数:fd:文件描述符
-
buf:将文件的身份信息存储到buf指定的空间里
-
返回值:成功 0错误 -1 errno被设置为相应错误码
struct stat
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6dnFudr-1615732028958)(http://img.wangzun233.top/uc02.png)]
st_mode
•t_mod
e成员,该成员描述了文件的类型和权限两个属性。
•15-12
位保存文件类型
•11-9
位保存执行文件时设置的信息
•8-0
位保存文件访问权限
st_mode的文件类型
-
S_IFMT 0170000 文件类型的位遮罩
-
S_IFSOCK 0140000 套接字文件
-
S_IFLNK 0120000 链接文件
-
S_IFREG 0100000 一般文件
-
S_IFBLK 0060000 块设备文件
-
S_IFDIR 0040000 目录
-
S_IFCHR 0020000 字符设备文件
-
S_IFIFO 0010000 管道文件
计算文件类型
S_IFMT
是一个掩码,它的值是0170000(注意这里用的是八进制), 可以用来过滤出前四位表示的文件类型。
通过掩码S_IFMT
把其他无关的部分置0,再与表示目录的数值比较,从而判断这是否是一个目录
计算文件类型宏
•为了简便操作,<sys/stat.h>中提供了宏来代替上述代码
- S_ISDIR() - 是否目录
- S_ISREG() - 是否普通文件
- S_ISLNK() - 是否软链接
- S_ISBLK() - 是否块设备
- S_ISCHR() - 是否字符设备
- S_ISSOCK() - 是否Unix域套接字
- S_ISFIFO() - 是否有名管道
文件权限
-
st_mode字段的最低9位,代表文件的许可权限,它标识了文件所有者、组用户、其他用户的读(r)、写(w)、执行(x)权限。
-
目录的权限与普通文件的权限是不同的。
-
目录读权限。读权限允许我们通过opendir()函数读取目录,进而可以通过readdir()函数获得目录内容,即目录下的文件列表
-
写权限。写权限代表的是可在目录内创建、删除文件,而不是指的写目录本身。
-
执行权限。可访问目录中的文件。
文件权限位
- S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
- S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
- S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
- S_IRGRP 00040 用户组具可读取权限
- S_IWGRP 00020 用户组具可写入权限
- S_IXGRP 00010 用户组具可执行权限
- S_IROTH 00004 其他用户具可读取权限
- S_IWOTH 00002 其他用户具可写入权限
- S_IXOTH 00001 其他用户具可执行权限
获取文件权限
struct stat st;
stat(path, &st);
st.st_mode & S_IXUSR == 1; *//**可以判断是否可执行*
其他位
- S_ISUID 04000 用户的id
- S_ISGID 02000 用户组的id
- S_ISVTX 01000 文件的sticky位
- 若一目录具有sticky 位 (S_ISVTX), 则表示在此目录下的文件只能被该文件所有者、此目录所有者或root 来删除或改名.
时间函数
#include <time.h>
char *ctime(const time_t *timep);
-
功能:将秒数转换为正常的字符串格式的日历时间
-
参数:timep,这是指向 time_t 对象的指针,该对象包含了一个日历时间。
-
返回值:该函数返回一个 C 字符串,该字符串包含了可读格式的日期和时间信息
•案例
• char* time = ctime(&st.st_mtime);//获取最后一次修改时间
Linux下用户的信息
•在/etc/passwd 文件下保存了用户的信息
•第一列 用户的名字
•第二列 用户是不是有密码
•第三列 用户的id uid
•第四列 用户的初始组id gid
•第五列 用户的注释信息
•第六列 用户的家目录
•第七列 用户执行的第一个程序
•一个用户可以属于多个组 一个用户组包含多个用户
获取用户信息
•getpwuid(3)
•#include <sys/types.h>
•#include <pwd.h>
•struct passwd *getpwuid(uid_t uid);
•功能:找到跟uid匹配的用户信息
•参数:uid:指定要找的用户的id
•返回值:找不到uid指定的用户或者错误产生NULL如果是错误产生errno的值被设置为相应错误码,找到非NULL
Linux下的组信息
•Linux下在/etc/group文件中保存了组的信息
•第一列 用户组的名字
•第二列 用户组是否有密码
•第三列 用户组的组id
第四列 用户组的成员
获取组信息
•#include <sys/types.h>
•#include <grp.h>
•struct group *getgrgid(gid_t gid);
•功能:getgrgid()用来依参数gid 指定的组识别码逐一搜索组文件
•参数:gid-组id号
•返回值:返回 group 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生.
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* NULL-terminated array of pointer to names of group members */
};
chmod/fchmod
•#include <sys/stat.h>
•int chmod ( const char* path, // 文件路径 mode_t mode // 文件权限 );
•int fchmod ( int fd, // 文件路径 mode_t mode // 文件权限 );
•成功返回0,失败返回-1。
•mode为以下值的位或(直接写八进制整数形式亦可, 如07654 - rwSr-sr-T):
•S_IRUSR(S_IREAD) - 属主可读
•S_IWUSR(S_IWRITE) - 属主可写
•S_IXUSR(S_IEXEC) - 属主可执行
•S_IRGRP - 属组可读
•S_IWGRP - 属组可写
•S_IXGRP - 属组可执行
•S_IROTH - 其它可读
•S_IWOTH - 其它可写
•S_IXOTH - 其它可执行
rename重命名
•#include <stdio.h>
•int rename(char * oldname, char * newname);
功能:用于重命名文件、改变文件路径或更改目录名称,其原型为
•参数:oldname为旧文件名,newname为新文件名。
•返回值:修改文件名成功则返回0,否则返回-1。
ftruncate函数
•#include <unistd.h>
•int ftruncate(int fd, off_t length) 。
•功能:改变文件length的大小
•参数:fd打开的文件描述符,
• length想要扩展的文件大小,如果length小于原文件大小,则原文件超过length的内容被删除
•返回值:成功返回0,失败返回-1,errno被设置
文件的映射
- mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程
mmap函数
#include <sys/mman.h>
•void* mmap (
• void* start, // 映射区内存起始地址// NULL系统自动选定,
• size_t length, // 字节长度,自动按页(4K)对齐
• int prot, // 映射权限
• int flags, // 映射标志
• int fd, // 文件描述符
off_t offset // 文件偏移量,自动按页(4K)对齐
);
•功能:将文件或设备映射到进程的虚拟地址空间
•成功返回映射区内存起始地址,失败返回MAP_FAILED(-1)。
mmap函数参数
•prot取值:
•PROT_EXEC - 映射区域可执行。
•PROT_READ - 映射区域可读取。
•PROT_WRITE - 映射区域可写入。
•PROT_NONE - 映射区域不可访问。
•flags取值:
•MAP_SHARED - 对映射区域的写入操作直接反映到文件中。共享映射
•MAP_PRIVATE - 对映射区域的写入操作只反映到缓冲区中, 不会真正写入文件。
MAP_ANONYMOUS - 匿名映射,将虚拟地址映射到物理内存而非文件,忽略fd
munmap函数参数
•int munmap(void *addr, size_t length);
•功能:解除文件或设备到进程的虚拟地址空间的映射
•参数:addr:指定了映射区域的起始地址
• length:指定了映射区域的长度
返回值:成功 返回0错误 -1 errno被设置。
mmap
-
一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。
•分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。但是5000-8191写入的字节不会再文件中显示。
-
一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。
•分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。总线错误
-
一个文件初始大小为0,使用mmap操作映射了10004K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr
•分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。
•但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。
目录函数
opendir函数
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
-
功能:打开一个文件夹
-
参数:name:指定了要打开的文件夹的名字
-
返回值:错误 NULL errno被设置成功 返回一个指向文件夹流的地址文件夹流的读写位置定位在文件夹的第一个条目
closedir函数
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
-
功能:关闭一个文件夹
-
参数:dirp:指定要关闭的文件夹流
-
返回值:成功 0错误 -1 errno被设置为合适的错误码
readdir函数
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
-
函数功能:表示根据参数指定的位置读取目录中的内容,
-
返回值:成功返回struct dirent类型的指针,失败返回NULL
•struct dirent {
• ino_t d_ino; /*i节点编号*/
• off_t d_off; /*在目录文件中的偏移*/
• unsigned short d_reclen;/*文件名长*/
• unsigned char d_type;/*文件的类型*/
• char d_name[256]; /*文件名称*/
•};
其他目录操作函数
-
getcwd() - 获取当前程序所在的工作目录
-
mkdir() - 创建一个目录
-
rmdir() - 删除一个空目录
-
chdir() - 切换目录,它只对该进程有效,而不能影响调用它的那个进程。在退出程序时,shell还会返回开始时的那个工作目录。
进程管理
进程的相关概念
•程序:主要指存放在硬盘上的可执行文件 ,用来描述要完成的功能。
•进程:进程是程序实体的运行过程,是系统进行资源分配和调度的一个独立单位。
•同样一个程序,同一时刻被两次运行,那么他们就是两个独立的进程。
•系统资源以进程为单位分配,如内存、文件等,操作系统为每个独立的进程分配了独立的地址空间
•系统采用PID唯一标识一个进程,在每一个时刻都可以保证PID的唯一性,采用延迟重用的策略
•操作系统将CPU调度给需要的进程,即将CPU的控制权交给某个进程就称为调度。
•系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block)
时间片轮转算法
•时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。
进程控制块-PCB
•PCB是系统感知进程存在的唯一标志:进程与PCB一一对应;
•是进程管理和控制的最重要的数据结构(进程描述信息 、处理机状态信息、进程调度信息、进程控制信息);
•进程描述信息
–进程标识符(pid),这个标识是唯一的,通常是一个整数
–进程名,通常基于可执行文件名,这是不唯一的
–用户标识符(uid)
–进程组关系
进程的控制信息
•当前状态
•优先级
•代码执行入口地址
•程序的磁盘地址
•运行统计信息(执行时间、页面调度)
•进程间同步和通信
•进程的队列指针
•进程的消息队列指针
进程三种基本状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4noztRQJ-1615732028960)(http://img.wangzun233.top/uc04.png)]
进程的五状态模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4xPnISh-1615732028962)(http://img.wangzun233.top/uc05.png)]
进程的相关命令
•ps - 查看当前终端中的进程
•whereis 命令 表示查看指定命令所在的位置
•ps -aux 表示显示所有包含其他使用者的进程
•ps -aux | more 表示分屏显示命令执行的结果
•USER - 用户名,也就是进程的属主 PID - 进程的进程号 %CPU - 进程占用的CPU百分比 %MEM - 进程占用的内存百分比 STAT - 进程的状态信息 TIME - 消耗CPU的时间 COMMAND - 进程的名称
•其中进程的状态信息主要有: S 休眠状态 s 进程的领导者 Z 僵尸进程 R 正在运行的 O 可运行状态 T 挂起状态 < 优先级高的进程 N 优先级低的进程 L 有些页被锁进内存, + 位于后台的进程组;
•ps -ef 表示以全格式的方式显示进程
•kill -9 进程号 表示杀死指定的进程
父子进程
•如果进程A启动了进程B,那么进程A叫做进程B的父进程,而进程B叫做进程A的子进程
•一般来说,进程0是系统内部的进程,负责启动进程1和进程2,而其他的所有进程都是直接/间接地由进程1和进程2启动起来,而进程1也就是init进程
•父进程终止子进程也会被终止。
•如果父进程终止,子进程没终止,这种进程成为孤儿进程,子进程的父进程ID会自动指向init进程
•如果子进程死亡,父进程存活,并且没有回收子进程的资源,这种子进程被称为僵尸进程。
•前台后台进程。
进程的ID获取
#include <sys/types.h>
#include <unistd.h>
getpid() - 表示获取当前进程的进程号
getppid() - 表示获取当前进程的父进程ID
getuid() - 表示获取用户ID
getgid() - 表示获取组ID 。
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = getpid();//获取当前程序的进程号
pid_t ppid = getppid();//获取当前进程的父进程
printf("当前进程ID%d,父进程ID%d\n",pid,ppid);
while(1);
return 0;
}
//ps -ef
进程的创建-frok
#include <unistd.h>
pid_t fork(void);
•函数功能:表示以复制当前运行进程的方式去创建一个新的子进程。
•返回值:如果成功父进程返回子进程的PID,子进程返回0,失败返回-1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
printf("我是父进程,我的ID是%d\n",getpid());
printf("我要创建一个子进程\n");
pid_t pid = fork();//创建一个子进程,
// printf("我的ID是%d,fork的返回值是%d\n",getpid(),pid);
if(pid==-1){
perror("fork");
return -1;
}
else if(pid==0){
//子进程运行的代码
printf("我的ID是%d,我的父进程ID是%d\n",getpid(),getppid());
}
else{
printf("我的ID是%d,我的子进程ID是%d\n",getpid(),pid);
//sleep(1);
}
printf("进程%d要结束了\n",getpid());
return 0;
}
代码执行的方式
•fork函数之前的代码,父进程执行1次
•fork函数之后的代码,父子进程各自执行1次
•fork函数的返回值,父子进程各自返回1次,也就是父进程返回子进程的PID,子进程返回0,可以通过返回值区分父子进程
•父子进程之间的执行顺序是不确定的,由操作系统决定。
父子进程的资源
•使用fork创建的进程,也会分配4G的虚拟空间,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程,两者的虚拟空间不同,但其对应的物理空间是同一个。
•当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,内核也会给子进程的数据段、堆栈段分配相应的物理空间,而代码段继续共享父进程的物理空间(两者的代码完全相同)
•总结:使用fork创建的子进程会复制父进程中除了代码区之外的其他内存区域,而代码区和父进程共享
父子进程的关系
•父进程启动了子进程之后,父子进程同时执行,如果子进程先结束,会给父进程发信号,父进程负责帮助子进程回收子进程的资源(善后)
•如果父进程先结束,子进程会变成孤儿进程,子进程会变更父进程为init进程,也就是进程1,init进程叫做孤儿院 ,(Ubuntu会被upstart回收,upstart是在图形化界面下的一个后台的守护程序)
•如果子进程先结束,但是父进程由于各种原因没有收到子进程发来的信号,没有进行资源的回收,那么子进程变成僵尸进程
fork函数扩张
•如何创建4个进程?
–fork(); fork(); 调用两次
–1个父进程 2个子进程 1个孙子进程
•如何创建3个进程?也就是1个父进程 2个子进程
–fork(); 1个父进程 和 1个子进程
–if(父进程) { fork(); 1个父进程 又创建 1个子进程 }
•俗称:fork炸弹
–while(1) {
–fork(); //进程数采用指数级增长方式
– }
进程的终止
•正常终止
–在main函数中执行了return 0
–调用exit()函数
–调用_exit()/_Exit()函数
–最后一个线程返回
–最后一个线程调用了pthread_exit()函数
•非正常终止
–采用信号终止进程
–最后一个线程被其他线程取消
//vim exit.c
#include <stdio.h>
#include</