admin管理员组文章数量:1593489
一、前言
参考书籍:UNIX环境高级编程(第三版) UNP(套接字) APUE 深入理解计算机系统
二、I/O(input & output 一切实现的基础)
1、stdio标准IO
stdio:FILE类型贯穿始终
FILE的结构(保密)
①FILE *fopen(const char *path,const char *mode)
path指文件路径
mode指操作类型
如果函数运行成功,返回一个FILE指针,如果失败返回一个空指针并且设置errno,
(errno是一个全局变量,用于全局的错误代码,必须实时打印)
eg:
- end of file 是指文件最后一个字节的下一个字节
- 在mode选项中,若在windows环境下编程,需要在加‘b’指定为二进制流,否则默认为文件流,在linux环境下只有一种流定义
- 在使用molloc函数时,如果报错显示等号两边类型不匹配,可能是没有包含malloc函数的头文件
- errno的具体错误的含义在/usr/include/asm-generic/errno-base.h中可以查看
- 函数①的return值返回的FILE指针指向的区域是在(堆 栈 静态区):如下
/********如果在栈上********/
FILE *fopen(const char *path,const char *mode)
{
FILE tmp;
tmp. = ;
....
return ;
}
//如果放在栈上,tmp用完之后就要释放掉,所以必须是一个
结构体,在一次赋值后return &FILE,但是函数调用完就会释放
所以返回后FILE已经没有东西了
/******如果放在静态区上*****/
FILE *fopen(const char *path,const char *mode)
{
static FILE tmp;
tmp. = ;
....
return ;
}
//如果放在静态区上,需要修饰为static,那么FILE在静态区
已经有值,所以每次调用该函数都会重复对同一块地址进行操作
/*****如果放在堆上******/
FILE *fopen(const char *path,const char *mode)
{
FILE *tmp;
tmp=malloc(sizeof(FILE));
....
return tmp;
}
//如果放在堆上,那么数据被存储在一个二叉树上,函数每次给tmp
分配一块内存挂在堆上,在调用fclose时删除tmp,大多数有互为可逆
操作的函数且返回值为指针,那么返回值都是放在堆上
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *fp;
fp=fopen("tmp","r");
if(fp==NULL)
{
// fprintf(stderr,"fopen() failed errno- %d\n",errno);
// perror("fopen()");可以打印错误原因,自动关联全局变量errno
fprintf(stderr,"fopen() errno=%s\n",strerror(errno));
exit;
}
exit(0);
}
//几种报错函数
②int fclose(FILE *fp)
关闭文件fp,成功返回0,失败返回-1并且设置errno
eg:
- 是资源就有上限, 默认最多可以打开1024个文件,默认前三个为stdin、stdout、stderr,
查看上限设置:ulimit -a
- 设置TAB键缩进,vim ~/.vim 中打开vimrc
- ll查看文件夹下所有文件的属性; ls -l查看文件属性
- 文件属性计算公式:0 666 & ~umask(0 002)
③int fgetc(FILE *stream)
int getc(FILE *stream) 定义为函数,fgetc被定义为宏,两者是一样的
int getchar(void)相当于fgetc(stdin)
成功则返回读到的字符的int形式或者EOF,失败返回error
④int fputc(int c,FILE *stream)
int putc(int c,FILE *stream)被定义为函数,fputc定义为宏
int putchar(int c)相当于putc(c,stdout)
eg:
查看两个文件是否相同命令:diff 文件1 文件2
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char **argv)
{
FILE *fps=NULL;
FILE *fpd=NULL;
int ch;
if(argc<3)
{
fprintf(stderr,"puts error");
exit(1);
}
fps=fopen("argv[1]","r");
if(fps==NULL)
{
perror("fopen(fps)");
exit(0);
}
fpd=fopen("argv[2]","w");
if(fpd==NULL)
{
perror("fopen(fps)");
exit(0);
}
while(1)
{
ch=fgetc(fps);
if(ch==EOF)
break;
fputc(ch,fpd);
}
fclose(fpd);
fclose(fps);
exit(0);
}
//实现一个mycopy函数
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char **argv)
{
FILE *fp=NULL;
int count=0;
int ch=0;
if(argc<2)
{
fprintf(stderr,"inputs error");
exit(1);
}
fp=fopen(argv[1],"r");
if(fp==NULL)
{
perror("fopen(fp)");
exit(1);
}
while(ch!=EOF)
{
ch=fgetc(fp);
count++;
}
prinf("%d",count);
exit(0);
}
//计算文件中字符个数
⑤char *fgets(char *s,int size,FILE *stream)
在stream中读取字节到s中,最多读取size个字节;读到文件尾返回NULL,出错返回errno,正确时返回s
函数正常结束的情况:读了(size-1)个字节/读到'\n'
#define SIZE 5
fgets(buf,SIZE,stream)
//读到size-1个字节,预留一个尾'\0'
//读到'\n'
buf='ab'时,fgets时输入的是'a b \n \0'
buf='abcd'时,fgets需要读两次,第一次为'a b c d \0 ',第二次为'\n \0 .....'
⑥int fputs(const char *s,FILE *stream)
将字符串输出到stream中,返回非负整数,如果返回EOF(-1)则出错
⑦size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
从stream中读字节到ptr中,读nmemb个对象每个对象size的大小
返回成功读到的对象的个数,如果读到的不足一个对象返回0或者errno
⑧size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream)
将ptr中的字节写入到stream中,写入nmemb个对象,每个对象size个字节
返回成功写到的对象的个数,如果写到的不足一个对象返回0或者不足对象的所含字符个数
⑨int fprint(FILE *stream,const char *format,...)
将...以format的形式输出到stream里
int sprintf(char *str,const char *format,...)输出到字符串str里(可以实现与itoa相反的效果,将任何形式转换成str字符串形式)
int snprintf(char *str,size_t size,const char *format,...)输出到字符串中去,这块str空间大小为size
eg:
- 重定向输出:echo 1 > 文件名
- 查看文件新增内容: cat 文件名
- int atoi(const char *nptr)将字符串转化成整型数 ‘1223’--->1223
- 一般写入或读取时都是一行一行为终止,所以在return时'\n'会进行特殊处理
⑩int fsanf(FILE *stream,const char *format,...)
int sanf(const char *format,...)缺点是看不见进来的输入有多大
int sscanf(const char *str,const char *format,...)将缓冲区上的信息输入到str
⑪int fseek(FILE *stream,long offset,int whence)
定位 文件位置指针,offset是偏移量(+向前,-向后 ),whence是偏移位置包括(SEEK_SET,SEEK_CUR,SEEK_END)
long ftell(FILE *stream)
在使用ftell或者fseek时,如果long是32位,则文件大小最多为4G才能有效查找
void rewind(FILE *stream)
文件位置指针定位到文件首
空洞文件(文件内字符都为‘\0’)直接使用fseek将位置定位在文件后2G占住空间
eg:
- 如果在使用函数时需要加入宏定义时,可以直接在编译命令加:gcc a.c -o a -D_FILE_OFFSET_BITS=64,或者在makefile中加入:CFLAGS+=-D_FILE_OFFSET_BITS=64(注意-D是命令)
- 查看文件属性:ls -l 文件名
⑫int fflush(FILE *stream)
如果stream是NULL,那么会刷新所有的输出流(直接刷新缓冲区,例如让尚在缓冲区的输出直接输出,不用等待程序运行结束)
eg:
- 缓冲区的作用:大多数情况下是好事,合并系统调用,见setvbuf函数
行缓冲:换行时候刷新,满了的时候刷新,强制刷新(标准输出是这样的,因为是终端设备)
全缓冲:满了的时候刷新,强制刷新(默认,只要不是终端设备)
无缓冲:如stderr,需要立即输出的内容
- 光标停在函数,shift+k可以直接跳转到man手册
⑬ssize_t getline(char **lineptr,size_t *n,FILE *stream)
传入一个字符串地址和一个整型数地址,可以读到一整行的数,返回读到的字符个数,包括'\n',但是不包括'/0'。失败返回-1(此函数只是不断在申请内存,并没有释放内存的操作)
#include <stdio.h>
#include <stdlb.h>
#include <string.h>
int main(int argc,char **argv)
{
char *linebuf=NULL;
size_t linesize=0;
FILE *fp;
if(argc<2)
{
fprintf(stderr,"Usage ...\n);
exit(1);
}
fp=fopen(argv[1],"r");
if(fp==NULL)
{
perror("fopen");
exit(1);
}
while(1)
{
if(getline(&linebuf,&linesize,fp)<0)
break;
printf("%d\n",strlen(linebuf);
printf("%d\n",linsize);
}
fclose(fp);
exit(0);
}
⑭临时文件
1、如何不冲突 2、及时销毁
tmpnam:创建一个函数名,然后调用其他函数打开文件,没有实现原子操作,可能导致冲突
FILE *tmpfile(void):创建一个无名文件,以w+r形式打开,可以避免冲突,并且将FILE使用fclose之后,由于FILE的可指向路径计数器count为0的时候这快空间自动释放,可以做到及时销毁。
eg:
打开所有隐藏文件:ls -a
2、系统调用IO(文件IO)
①文件描述符的概念
在一个进程中,会分配单独的一块指针数组,数组中每个元素指向一个文件位置指针相关结构体执指向文件结构体,文件描述符就是数组的下标;一般数组的前三个位置分别是stdin,stdout,stderr;默认分配新的文件描述符时,首先分配可用的下标最小的数组元素。
eg:一般情况下,一个进程空间分配1024个文件描述符
②文件IO的操作:open,close,read,write,lseek
int open(const char *pathname,int flags)
flags包含:O_RDONLY,O_WRONLY,O_RDWR等
R -->O_RDONLY
R+ -->O_RDWR
W -->O_WRONLY|O_CREAT|O_TRUNC
w+ -->O_RDWR|O_CREAT|O_TRUNC
a -->O_WRONLY|O_CREAT|O_APPEND
a+ -->O_RDWR|O_CREAT|O_APPEND
int open(const char *pathname,int flags,mode_t mode)
int creat(const char *pathname,mode_t mode)
ssize_t read(int fd,void *buf,size_t count)
ssize_t write(int fd,const void *buf,size_t count)
off_t lseek(int fd,off_t offset,int whence)
#include <stdio.h>
#include <stdlib.h>
#.......
#define BUFSIZE 1024
int main(int argc,char **argv)
{
int fds,fdd;
char buf[BUFSIZE];
int ret,pos,len;
if(argc<3)
{
fprintf(stderr,"Usage ...\n");
exit(1);
}
fds=open(argv[1],O_RDONLY);
if(fds<0)
{
peeror("open()");
eixt(1);
}
fdd=open(argc[2],O_WRONLY|O_CREAT|O_TRUNC,0600);
if(fdd<0)
{
close(fds);
peeror("open()");
eixt(1);
}
while(1)
{
len=read(fds,buf,BUFSIZE);
if(len<0)
{
perror("read()");
break;
}
if(len==0)
{
break;
}
pos=0;
while(len>0)
{
ret=write(fdd,buf+pos,len);
if(ret<0)
{
perror("write()");
exit(1);
}
pos+=ret;
len-=ret;
}
}
close(fdd);
close(fds);
exit(0);
}
eg:
- 阻塞IO:一直等待直到这个阶段执行结束;非阻塞IO:如果遇到问题可以跳过
- 在open中函数名相同但是函数的参数不同的现象:变参函数
如何识别重载和变参函数的形式:在输入多个参数时,系统不报错,则函数也不知道自己有几个参数,那么使用变参函数,如果系统报错,则函数的参数个数时固定的,使用重载
③文件IO与标准IO的区别:
响应速度&吞吐量,标准IO设置缓冲区,在缓冲区满或其他紧急情况时与内核交互,所以响应速度慢,文件IO及时与内核交互,只要有一个任务就调用一次内核,响应速度快,从而吞吐量低;文件IO与标准IO不可混用(有无缓冲区情况下pos指针位置不同),转换函数有fdopen,fileno
eg:
- 查看文件的系统调用路径:strace 文件名
- 查看命令执行时间:time 命令
real:real=user+sys+调度等待
user:程序在user层面消耗的时间
sys:程序在进行时在系统调用层面花费的时间
④文件共享
多个程序同时操作同一个文件
尝试面试题:删除一个文件的第十行,补充知识点:truncate
⑤原子操作:不可分割的操作
原子:不可分割的最小单位
原子操作的作用:解决竞争和冲突
⑥程序中的重定向
dup,dup2,拷贝一个文件描述符到指针数组的最小下标位置,其中dup2是(dup+close)的原子操作。
⑦同步
sync全局催促,fsync,fdatasync
数据与亚数据:数据是指正常的数据内容,亚数据是指文件的运行时间、大小等
⑧fcnti():文件描述符相关的函数几乎都来自于该函数
ioctl():设备相关的内容都与此函数相关
/dev/fd/目录:虚目录,显示当前进程的文件描述符的信息
三、文件系统
1、目录和文件
①获取文件属性
stat:通过文件路径获取属性,面对符号链接文件时获取的时所指向目标文件的属性
fstat“通过文件描述符获取属性
lstat:面对符号链接文件时获取的时符号链接文件的属性
eg:
创建一个-b名字的文件可以用命令:touch -- -b或者touch ./-b(--表示当前命令结束)
删除时可以用命令:rm -- -b或rm ./-b(./表示路径)
②文件的访问权限
st_mode是一个16位的位图,用于表示文件类型,文件访问权限,及特殊权限位,详见man 2 stat、man 7 inode
③umask
防止产生权限过松的文件,具体原理:0666 & ~umask
可以用命令:umask 0XXX 来改变umask的值,详细可见:man 2 umask
④文件权限的更改
chmod XXX 文件名 常用的权限为664
chmod X+X 文件名 第一个X指定chmod对象,为o(other),g(group),u(user),a(all);第二个X指定chmod的形式x(可执行),w(可写),r(可读)。
可以在函数中使用chmod、fchmod改变文件权限
⑤粘住位
t位,在内存中保留它的使用痕迹,一般在目录中会设置
⑥文件系统:FAT,UFS
文件系统:文件或数据的存储和管理
FAT16/32:静态单链表(arr)
eg:
市面上传统的磁盘清理比如360,只是将占用内存空间不断挤兑,导致大量内容流向swap临时区域,并不能达到什么效果,在之后挤兑走的内容又会回来
关于磁盘分区,是因为在古老的FAT文件系统中由于单链表中元素(int next[N],int buf[N])中N的大小的限制,一个整体区域中无法存储过多的文件,所以需要分区,但是现在除了U盘\SD卡很少有FAT系统,所以不需要分区
关于内存整理,用户是无法直接伸手去操作硬件的,所谓内存整理只是在用户层与硬件层之间的对话层做了一些调整,相当于掩耳盗铃
详细可以见(李慧芹 p146文件属性和FAT文件系统)
FAT文件系统:头 | Inode位图 | 数据块位图 | Inode | 数据块
Inode位图对应Inode,用Inode位图表示Inode区域某一个Inode是否被使用
数据块位图同上
⑦硬链接,符号链接
硬链接与目录项时同义词,且建立硬链接有限制,不能给分区建立,不能给目录建立
符号链接优点:可跨分区,可以给目录建立
硬链接:ln 源文件 新文件
为一个Inode创建硬链接,使得创建的文件名指向Inode
符号链接:ln -s 源文件 新文件
生成一个符号链接,相当于windows里面的快捷方式
函数:link unlink
创建临时文件的一种方法:tmpname-->fopen->unlink将其从磁盘删除
remove:相当于rm,删除文件
rename:相当于mv,改变文件名字或者位置
⑧utime
可以更改文件的最后读时间和最后写时间
int utime(const char *filename,const struct utimbuf *times)
放在struct utimbuf { time_t actime, time_t modtime }
Access 意思是“访问”。
在终端上用cat、more 、less、grep、sed、 cp 、file 一个文件时,此文件的Access的时间记录都会被更新(空文件例外),纯粹的access是不会影响modify和change,但会受到modify行为的影响。
用ls -lu看到的文件时间是最近一次access的时间。对于目录而言,只是进入目录的话不会改变它的access时间,但只要用ls查看了此目录的内容(无论在何处),这个目录的access时间就会被更新。
Modify 意思是“更改(内容),“或者“写入”。
当更改了一个文件的内容的时候,此文件的modify的时间记录会被更新。
Change 改变(状态或属性)。
对一个文件或者目录作mv、chown、chgrp操作后,它的Change时间记录被更新,change时间会受到modify行为的影响。用ls -lc看到的文件时间是最近一次change的时间。
⑨目录的创建和销毁
mkdir
rmdir
⑩更改当前工作路径
chdir:改变文件的工作路径
fchdir:同上,但是时利用fd
chroot:改变root,但是chdir和fchdir可以找到真正的root
getcwd:相当于pwd
⑪分析目录/读取目录内容
int glob(const char *pattern,int flags,int (*errfunc) (const char *epath,int eerrno,glob_t *pglob)
void globfree(flob_t *pglob)
opendir()
closedir()
readdir()
rewinddir()
seekdir()
telldir()
eg:
glob使用实例
#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#define PAT /etc/a*.conf
int mian()
{
glob_t globres;
int i,err;
err=glob(PAT,0,NULL,&globres);
if(err)
{
printf("Error code");
exit(1);
}
for(i=0li<globres.gl_pathc;i++)
{
puts(globres.gl_pathv[i]);
}
exit(0);
}
将目录作为流进行操作解析:
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#define PAT "/etc/"
int mian()
{
DIR *dp;
struct dirent *cur;
dp=opendir(PAT);
if(dp==NULL)
{
perror("opendir()");
exit(1);
}
while((cur=readdir(dp))!=NULL)
{
puts(cur->d_name);
}
closedir(dp);
exit(0);
}
获取文件大小:du 文件名 比如:du / du/etc / du makefile
du获取的大小包括:此路径下所有文件的大小以及该路径下目录的大小
2、系统数据文件和信息
①/etc/passwd
通过id或者name查找用户的相关信息
getpwuid();
getpwnam();
②/etc/group
通过id或者name查找group的相关信息
getgrgid();
getgrgrnam();
③/etc/shadow
查找用户的密码加密信息
第一个$与第二个$之间的数字表示加密方式,1表示MD5
第二个$与第三个$之间的数字表示混杂字串,用来和密码或,得出混杂后的密码
第二个$与第三个$之间的数字表示混杂密码通过某种加密方式得到的结果
struct spwd *getspnam(const char *name);
char *crypt(const char *key , const char *salt); key值为密码;salt为加密方式和混杂字串写作:$id$salt$
getpass(); 获取口令
eg:密码加密应用实例
#include <stdio.h>
#include <crypt.h>
#include <unistd.h>
#include <shadow.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
struct spwd *pass_info=NULL;
char *password=NULL;
char *encode=NULL;
if(argc!=2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
password=getpass("Input:");
pass_info=getspnam(argv[1]);
if(pass_info==NULL)
{
perror("getspnam()");
exit(1);
}
encode=crypt(password,pass_info->sp_pwdp);
if(strcmp(encode,pass_info->sp_pwdp)==0)
printf("ok\n");
else
printf("no ok\n");
exit(0);
}
//需要在root权限下进行操作,否则无法读取到shadow文件下的内容
//运行时需要添加链接,但无法添加到makefile中。智能采用:gcc *.c -o * -lcrypt
④时间戳
time_t char * struct tm 以下函数实现将时间戳由 字符串、大整数、结构体之间进行转换
time() 以秒为单位获取时间(从1970.1.1开始)
gmtime()
localtime()
mktime() 参数没有const修饰,可以随意更改
size_t strftime() 格式化写入 时间或者日期
3、进程环境
①main函数
int main(int argc,char **argc)
②进程的终止
正常终止:
- 从main函数返回;
- 调用exit函数;
eg:
使用echo $?可以查看shall进程上面最后一条命令的返回值
所有函数的返回值都是服务于其父进程的,main函数的return一般给shell看
void exit(int status)这个函数将status & 0377的值返回给父进程,此公式的运算结构就是返回一个八位二进制数,即保留低8位,正好等于char类型的数据,所以status值为-128~127
- 调用_exit或者_Exit;
eg:
在程序结束后调用_exit或者_Exit,直接跳转到内核实现终止,不执行钩子函数,不进行内存清理
如果调用exit则需要先终止各种处理程序最后调用标准I/O清理程序,最后在调用_exit或_Exit进行终止
- 最后一个线程从启动例程返回;
- 最后一个线程调用pthread_exit
异常终止:
- 调用abort;
- 接受到一个信号并终止;
- 最后一个线程对其取消请求并作出相应
钩子函数:
int atexit(void (*function)(vo id)):所有被atexit()和on_exit()注册的函数,都会以逆序注册到exit()函数,在最后exit结束之后会按逆序调用之前被atexit()注册过的函数
#include <stdio.h>
#include <stdlib.h>
static void f1(void)
{
puts("f1() is working");
}
static void f2(void)
{
puts("f2() is working");
}
static void f3(void)
{
puts("f3() is working");
}
int main()
{
puts("begin");
atexit(f1);
atexit(f2);
atexit(f3);
//将f1,f2,f3挂到钩子函数上
puts("end");
exit(0);
//在运行到exit时首先将钩子函数上的函数逆序运行,f3,f2,f1
}
③命令行参数的分析
getopt()
getopt_long()
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include<string.h>
#define TIMESIZE 1024
#define FMTSIZE 1024
int main(int argc,char **argv)
{
time_t stamp;
struct tm *tm;
char timestr[TIMESIZE];
char fmtstr[FMTSIZE]={};
int fmt;
FILE *fd=stdout;
while(1)
{
fmt=getopt(argc,argv,"-H:MSy:md");
if(fmt<0)
break;
switch(fmt)
{
case 1:
if(fd==stdout)
{
fd=fopen(argv[optind-1],"w+");
if(fd==NULL)
{
fd=stdout;
}
}
break;
case 'H':
if(strcmp(optarg,"12")==0)
strncat(fmtstr,"%I(%P) ",FMTSIZE-1);
else if(strcmp(optarg,"24")==0)
strncat(fmtstr,"%H ",FMTSIZE-1);
else
fprintf(stderr,"invaild argument of -H");
break;
case 'M':
strncat(fmtstr,"%M ",FMTSIZE-1);
break;
case 'S':
strncat(fmtstr,"%S ",FMTSIZE-1);
break;
case 'y':
if(strcmp(optarg,"2")==0)
strncat(fmtstr,"%y ",FMTSIZE-1);
else if(strcmp(optarg,"4")==0)
strncat(fmtstr,"%Y ",FMTSIZE-1);
else
fprintf(stderr,"invaild argument of -y");
break;
case 'm':
strncat(fmtstr,"%m ",FMTSIZE-1);
break;
case 'd':
strncat(fmtstr,"%d ",FMTSIZE-1);
break;
default:
break;
}
}
stamp=time(NULL);
tm=localtime(&stamp);
strftime(timestr,TIMESIZE,fmtstr,tm);
strncat(timestr,"\n",TIMESIZE-1);
fputs(timestr,fd);
if(fd!=stdout)
fclose(fd);
return 0;
}
④环境变量(程序员与管理员之间的约定)
KEY=VALUE
extern char **environ
getenv() 获得环境变量的值
setenv() 改变或者添加环境变量
unsetenv() 删除环境变量à先把环境变量原来的值释放掉,然后去堆上申请一块新的内存用来存放环境变量
putenv() 同上setenv,但是传入参数没有const修饰,所以谨慎使用
eg:
查看环境变量:export
修改环境变量:环境变量=*** 例如:LANG=en
⑤C程序的存储空间布局
假设内存空间一共有4G,那么分配比例大约为:内核态1G + 用户态3G
eg:
查看进程号:ps axf
查看进程内存分配:pmap 进程号
⑥库
动态库
静态库
手工装载库:(插件)dlopen,dlclose,dlerror,dlsym
⑦函数跳转
goto()无法实现函数间的跳转
setjmp()
longjmp()带回到setjmp的值是非零值,若0则替换为1
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf save;
void c(void)
{
int val=9;
printf("%s:Begin.\n",__FUNCTION__);
longjmp(save,val);
printf("%s:End.\n",__FUNCTION__);
}
void b(void)
{
printf("%s:Begin.\n",__FUNCTION__);
printf("%s:call c.\n",__FUNCTION__);
c();
printf("%s:c is returned.\n",__FUNCTION__);
printf("%s:End.\n",__FUNCTION__);
}
void a(void)
{
int ret;
printf("%s:Begin.\n",__FUNCTION__);
ret=setjmp(save);
if(ret==0)
{
printf("%s:call b.\n",__FUNCTION__);
b();
}
else
printf("%s:already jump here\n",__FUNCTION__);
printf("%s:End.\n",__FUNCTION__);
}
int main()
{
printf("%s:Begin.\n",__FUNCTION__);
printf("%s:call a.\n",__FUNCTION__);
a();
printf("%s:a is returned.\n",__FUNCTION__);
printf("%s:End.\n",__FUNCTION__);
}
⑧资源的获取和控制
getrlimit()
setrlimit()
四、进程
1、进程标识符pid
类型pid_t
命令ps
进程号是顺次向下使用
eg:
获得当前进程号:getpid
获得父进程号:getppid
2、进程的产生
fork()通过复制父进程,创建一个子进程
fork后父子进程的区别:fork的返回值不一样;pid不同,ppid不同;未决信号和文件锁不继承;资源利用量清零。
init进程(1号进程):是所有进程的祖先进程
调度器的调度策略来决定那个进程先运行
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
printf("[%d]:begin!\n",getpid());
pid=fork();
if(pid<0)
{
perror("getpid()");
exit(1);
}
else if(pid==0)
{
printf("[%d]: child is working!\n",getpid);
}
else
{
printf("[%d]: parent is working!\n",getpid());
}
printf("[%d]:end!\n",getpid());
exit(0);
}
为什么输出到终端和输出到文件不一样:1)终端:终端是正常输出设备,采用行缓冲模式,所以在begin加‘\n’之后,父进程的缓冲区就空了,之后fork出来一个子进程,复制父进程的空缓冲区,那么就不会打印出第二次begin
2)文件:文件采用全缓冲模式,所以在begin加‘\n’之后,父进程的缓冲区就有东西了,之后fork出来一个子进程,复制父进程的缓冲区,那么就会打印出第二次begin且进程号是父进程的pid
所以就需要在打印begin之后加上fflush刷新缓冲区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define RIGHT 200
#define LEFT 0
int main()
{
pid_t pid;
int i,j,mark;
for(i=LEFT;i<RIGHT;i++)
{
pid=fork();
if(pid<0)
{
perror("fork()");
exit(1);
}
if(pid==0)
{
mark=0;
for(j=2;j<i/2;j++)
{
if(i%j==0)
{
mark=1;
break;
}
}
if(mark==0)
printf("%d is a primer\n",i);
exit(0);
}
}
exit(0);
}
vfork()
eg:
- 在子进程结束时要记住exit结束子进程,否则会创建阶乘个数个子进程
- 查看文件执行结果行数:./prime | wc -l
- 杀死进程:killall prime1(杀死这个执行文件相关的所有进程)
- 收尸:在父进程没有结束时,子进程已经结束,那么子进程处于Z僵尸状态,等待父进程回收在父进程已经结束但是子进程还没有结束的时候,那么子进程大概率处于S可打断睡眠状态,此时变为孤儿进程,全部托管到init祖宗进程中。
- 在最开始的fork中:关于资源,父子进程使用完全不同的两块空间储存相同的内容
- 在最开始的vfork中:关于资源,父子进程共同使用同一块空间
- 在改良过的fork中:读时共享,写时拷贝,父子进程同时使用一块空间,但是在进程写入时,要讲写入部分相关内容拷贝一份,在备份空间写入
3、进程的消亡和释放资源
pid_t wait(int *status);
等待进程状态发生变化, 阻塞类型,一直等待直到状态发生变化,收回进程空间
pid_t waitpid(pid_t pid,int *status,int options);
非阻塞类型,只要状态达到option要求就执行,否则跳过
waitid();
wait3();
wait4();
eg:
进程空间分配策略:分块;交叉分配;池类算法
4、exec函数族
运行一个二进制可执行文件,用一个新的进程替换一个旧的进程,但是pid不变
execl()
execlp()
execle()
execv()
execvp()
eg:
查找文件的路径:which 文件名/文件夹名
注意刷新缓冲区
在小程序中,子进程结束后到Z状态,如果父进程不进行回收,那么比如等到程序运行结束才能释放进程资源,所以在工程编程中,子进程申请空间之后要及时用wait进行进程回收释放。
glob的灵活运用:glob_t类型的结构体与main函数传参及其相似,可以用glob函数进行合理的应用,选择GLOB_NOCHECK、GLOB_APPEND.
strsep函数:将一个字符串分割成几个字符串,按照特定的标识,如“ \0\t”
5、用户权限及组权限(u+s,g+s)
执行命令chmod u+s, 就是针对某个程序任何用户都有读写这个程序的权限,可以像root用户一样操作,这个指令只对程序有效,如果用此权限放在路径上是无效的。
getuid()
geteuid()
getgid()
getegid()
setuid()
6、观摩课:解释器文件
解释文件(脚本文件)
开头必须为#!
比如:
#!/etc/bash
ls
cat /etc.shadow
date
将当前文件装载到bash解释器中,用bash解释下面出现的命令
7、system()
调用/etc/sh来解释命令,可以理解成few的封装
8、进程会计
acct()
9、进程时间
times()
10、守护进程
后台一直在跑的进程,脱离控制终端,PPID(父亲的父进程)是init进程,PID,PGID,SID都为同一个值
setsid()
getpgrp()
getpgid()
setpgid()
单实例守护进程:锁文件/var/run/name.pid
启动脚本文件:/etc/rc*...
11、系统日志
系统日志存储位置:/var/log/ 系统主日志文件:message
syslogd服务
openlog()
syslog()
closelog()
五、并发(信号、线程)
同步:在程序执行的时候已经知道下一步要发生什么事情
异步:在程序执行的时候无法指导下一步要发生什么,比如俄罗斯方块游戏
异步事件的处理:查询法(盲目不断查询)、通知法(通过某种信号或者标志得知异步事件到来)
没有严格意义上的通知法,在交通系统中红绿灯作为通知信号,但是如果不一致盯着红绿灯,那么也无法实现通知法
1、信号
1.1、信号的概念
信号是软件层面的中断。
信号的响应依赖于中断。
kill -l:查看信号,1-31是标准信号,34-64是实时信号
core文件:写入出错信息的文件,在limit -a中可以查看,一般设置为0
1.2、signal()
信号会打断阻塞系统调用
1.3、信号的不可靠
信号的行为不可靠:第一次调用还没有结束,第二次调用就开始了,现在的实现解决方案是加入一个链式结构。不同于递归
1.4、可重入函数
信号的行为函数在第一次调用还没有结束时,又开始第二次调用,如果两次调用不互相影响,那么称此函数为可重入。
eg:如果某函数有_r版本,那么原版本一定是不可重入,_r版本是可重入。
1.5 信号的响应过程
信号从收到到响应有一个不可避免的延迟
思考:如何忽略掉一个信号的?(在mask位图上将此信号相关位置为零,那么在pending位图因为信号来临后改变后与mask位图按位与后仍未0)
标准信号为什么要丢失?(因为当很多个相同的信号到来后,会将pending位图上先关位置为1,很多次,但只能相应一次)
标准信号的响应没有严格的顺序
eg:在star.c中,为什么一直按ctrl+c打印星号会更快:因为程序大部分时间在sleep的等待中,此时不断地打断sleep进程,但是手速永远没有系统响应快,所以程序不断在打断的间隙开始for循环中的printf,从而实现快速打印星号。
1.6、常用函数
int kill(pid_t pid, unt sig)
给一个进程发信号,根据PID的值决定是全局广播、组播、单播、
int raise(int sig)相当于 kill(getpid(),sig)
给当前进程发送一个信号
unsigned int alarm(unsigned int seconds)
当second到时,发送一个SIGALARM给正在被调用的函数
alarm是不可重入函数,总是执行最后一个·alarm函数
pause()
等待一个信号
eg:
计时器函数:time、alarm
system()
sleep()
1.7、信号集
1.8、信号屏蔽字/pending集的处理
1.9、扩展
sigsuspend()
sigaction()
setitimer()
1.10、实时信号
版权声明:本文标题:UNIX环境高级编程(IPv4流媒体广播项目) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1728154486a1147559.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论