预处理指令:
#pragma
head.h文件不能比当前文件新
#pragma GCC dependency "head.h"
不能使用单词add printf
#pragma GCC poison add printf
_Pragma等价于#Pragma
创建静态库:
1)gcc -c xxx.c 生成 .o 文件
2)ar -r lib静态库名.a xxx.o ...
3)使用静态库
将静态为文件 libxxx.a所在的路径配置到环境变量LIBRARY_PATH下
4)连接静态库
gcc xxx.o -l静态库名
如果环境变量LIBRARY_PATH没有配置静态库的路径,
可直接连接:gcc xxx.o -l静态库名 -L库文件的在路径
还可以直接连接:gcc xxx.o libxxx.a
共享库(动态库):
1)创建共享库
gcc -c -fpic xxx.c ... 生成目标 xxx.o
gcc -shared xxx.o -o lib库名.so
2)使用共享库
1.连接
gcc xxx.o -l库名 -L 共享库路径,或者把共享库文件所在的目录配置到环境变量LIBRARY_PATH
2.运行 a.out
要求共享库在LD_LIBRARY_PATH环境变量指定的路径下能找到共享库文件
-----------
ldd:
查看程序所依赖的动态库
预处理:#include<head.h>
默认位置(/usr/include) C_INCLUDE_PATH -I 指定的位置 CPATH
C++ : CPLUS_INCLUDE_APTH
连接:找库(静态库,共享库)
在LIBRARY_PATH中找 直接用gcc的选项直接指定: -L 位置
运行时:需要共享库LD_LIBRARY_PATH
静态库:
xxx.c gcc -cxxx.c ==> xxx.o
yyy.c gcc -cyyy.c ==> yyy.o
ar -r libmyku.a xxx.o yyy.o
myku.h
test.c : #include "myku.h"
gcc -c test.c
gcc test.c -lmyku -L . ==>a.out
共享库:
xxx.c gcc -c-fpic xxx.c ==> xxx.o
yyy.c gcc -c-fpic yyy.c ==> yyy.o
gcc -shared xxx.o yyy.o -o libmyku.so
myku.h
test.c : #include "myku.h"
gcc -c test.c
gcc test.o -lmyku -L . ==> a.out
要使a.out能够运行则在LD_LIBRARY_PATH中要有libmylu.so所在路径
或者加上-static参数进行静态编译
显式手工使用共享库:
错误处理:
errno.h
定义了全局变量 int errno
程序出现错误时(一般返回非0值, 或返回空指针)
需要错误信息时:
perror()
printf("%m");
strerror(errno);
不要根据errno判断程序是否出错,而是出错去查看errno以知道出现什么错误
环境变量:
在任意程序中都能使用一个全局变量:char** environ
在使用时:
extern char** environ;
环境表是一个字符串数组,最后一个元素是NULL(*environ中的最后一个元素)
int main(int argc, char** argv, char** env)
参数env和environ有同样效果
getenv(name);
putenv(str); //str : name=value
setenv(name,value,rewrite);
unsetenv(name);
clearenv(); //仅Linux支持
在程序中改变环境变量不会影响系统环境变量
内存分配:
内存分配:
STL,智能指针
|
new, delete(c++)
|
|
malloc, free(c)
|
|
brk, sbrk , mmap, munmap 用户级
----------------------------------------------
内核级
kmalloc vmalloc
|
|
get_free_page
STL分配内存和释放是自动的
vector<int> v;
sizeof(v) : 12
指针
当前数据量
最大容量
C++ : newdelete
new[] delete[]
C :malloc free
newmalloc的区别:
new : 1,分配内存空间
2,构造成员变量
3,构造函数
4,类型转换 void* ==> 用户类型
malloc : 只做内存分配
delete : 1,调用析构函数
2,释放空间
free : 只释放空间
malloc:
在内存中维护一个数据结构(链表)
一次malloc会形成一个链表的节点
struct mem {
分配的内存空间
上一块内存的地址
下一块内存的地址
此节点中分配的内存空间的大小
}
malloc系列函数分配的内存绝对不允许越界
一旦越界,就会破坏后台内存的数据结构
全局常量在代码区
局部常量在栈区
局部静态变量在全局区
内存管理:
DOS IBM-DOS MS-DOS 物理地址 非保护
WIN32 保护模式逻辑地址(虚拟地址) 用户程序不能访问物理地址
32位操作系统中地址由32个二进制位(4字节)
int* p = sbrk(4);
void* p = sbrk(0);//未分配空间的首地址
void* p = sbrk(-4);//释放空间
1,内存:
使用的地址是虚拟地址(逻辑地址),
要与真正的物理内存映射(对应)起来才可以使用,否则段错误
操作系统对内存的分配以页为单位,一页一般4K
2,malloc
malloc在后台是一个双向链表的数据结构,每一次调用形成一个链表接节点
小内存:
第一次给几十个页面,后面的调用会在此页中分配
大内存:
第一次调用分配所需要的页面,后期调用在本段内存的基础上扩充
3,brk/sbrk
后台维护一个位置,刚刚已分配空间的结束位置(下次要分配空间的开始位置)
sbrk(0);//下次要分配空间的开始位置
sbrk(4);//分配4个字节的空间,返回这4个字节的首地址
sbrk(-4);释放空间,把位置向前移4个字节
brk(ptr);//ptr是需要分配的空间的结束位置的地址,将后台维护的位置移到ptr处。此时用sbrk(0)返回的地址就是ptr
4、mmap
I/O:
文件描述符:
非负整数, 0 1 2 操作文件依据 代表一个打开的文件
每个文件描述符对应一个文件,
当open一个文件时,内核会在内存中船舰一个文件表
文件表记录文件状态,当前偏移量,指向文件系统(磁盘上)的i-node指针
对文件的操作主要是一些系统调用函数:
open read write close
文件描述符=open("文件名",[O_RDONLY, O_WRONLY, O_RDWR | O_APPEND |
O_CREAT, O_EXCL, O_TRUCK], 文件权限数字表示)
ssize_tsize = read(fd, void*, ssize_t);
ssize_tsize = write(fd, void*, ssize_t);
lseek:
改变文件偏移量,不会进行I/O操作
文件偏移量可以大于文件大小,这时对文件进行操作,造成文件空洞
文件空洞不会真正在磁盘上占用空间
dup
复制文件描述符,但并不赋值文件表
dup2
指定复制后的文件描述符,如果此文件描述符已被占用,则先关闭
sync/fsync/fdatasync
同步数据 功能如同标准C中的fflush
fcntl:
操作文件的一些特性
int(命令不同含义也不一样)=fcntl(fd, 命令(int), 没有/long/flock)
F_DUPFD 复制文件描述符,类似dup, dup2
fcntl(fd, F_DUPFD, 0) == dup(fd)
close(7); fcntl(fd, F_DUPFD, 7) == dup2(fd, 7)
前者非原子操作,后则原子操作
F_GETFD/F_SETFD 有特殊用处 exec系统函数联合使用
F_GETFL/F_SETFL 获取(修改)文件描述符的状态信息
O_RDONLY, O_WRONLY, O_RDWR, O_APPEND
int stat = fcntl(fd, F_GETFL, 0);
stat & O_APPEND 如果非零,则文件有O_APPEND属性
int r = fcntl(fd, F_SETFL, O_APPEND | O_NONBLOCK);
F_SETLKW?F_SETLK 测试(设置)在文件身上的锁(读锁,写锁)
对文件进行加锁和解锁
加锁:
加读锁, 加写锁
读锁: 共享锁 同时对一个文件可以加多个读锁
写锁: 独占锁 排他的,对一个文件区域最多只能加一个写锁
如果加上读锁可以再加读锁,但不能加写锁
如果加了写锁不可以再加读锁和写锁
fcntl(fd, F_SETLK, flock);
flock决定着加读锁还是写锁,还有加到什么地方
1.文件加锁:加在文件的某一个区域
读 写 fcntl(fd, F_SETLK, struct flock*);
struct flock{
shortl_type; //锁的类型:F_RDLCK, F_WRLCK,F_UNLCK
shortl_whence; //计算锁的位置:SEEK_SET,SEEK_CUR, SEEK_END
off_tl_start; //指定相对于whence的起始地址
off_tl_len; //指定长度
off_tl_pid; //锁定该区域的进程ID,如果使用F_SETLK,该成员无效
};
加锁之后,如果进程结束,或文件关闭,将会自动解锁
在某个区域加读锁后,其他进程可以继续加读锁,但不能加写锁
在某个区域加写锁后,其他进程不能加读锁,也不能加写锁
测试某个文件区域是否可以加指定的锁:
fcntl(fd, F_GETLK, struct flock*);
如果flock中的值发生改变,说明之前flock结构中设定的锁不可以
flock改变为:变成之前已有锁的数据,其中l_pid为之前加锁进程的ID
F_SETLK如果加锁失败,直接返回-1
F_SETLKW如果加锁失败,程序阻塞,直到加上锁为止
谁加的锁谁解锁,解锁方式F_SETLCK,传的flock结构中l_type为F_UNLCK
2.读取文件属性和状态
stat(file, struct stat*)
执行成功会把文件的所有属性和状态保存到stat结构体中
如果在一个保存了N个结构体信息,可用如下方式获取结构个数
stat(file, &st)
st.st_size/sizeof(Emp);
判断是目录还是文件
文件是不是目录,以及文件的权限,
文件的类型(目录,普通文件,字符设备文件,块设备文件,socket文件,管道文件),保存在st_mode中
S_ISDIR(st.st_mode);//判断是否为目录
S_ISREG(st.st_mode);//判断是否为普通文件
...
s.st_mode & S_IRUSR;//判断用户是否可读
s.st_mode & S_IFDIR;//判断是否为目录
fstat和stst一样,只不过第一个参数是文件描述符
lstat可以获取link文件的属性,
而stat和fstat获取的是link文件指向的文件属性
3.屏蔽字
umask(屏蔽字);//返回之前的屏蔽字
若umask(0022);
则open("a.txt", O_RDWR | O_CREAT, 0666);所创建的文件权限为0644
4.truncate修改文件长度
5.文件映射
1)文件要打开,得到文件描述符
2)文件的大小需要调整,等于或大于你要映射的物理内存空间,防止总线错误
3)映射
mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 文件偏移量);
4)将文件当内存使用
5)解除映射
munmap(p, size);
6.目录
mkdir
rmdir
chdir
DIR* opendir(const char*)
readdir//读取一个目录项(子目录,文件)
struct dirent {
long d_no;
char d_name[NAME_MAX+1];
}
struct dirent* p;
while(p=readdir(dir))//读取此目录中每一项,当读完时返回NULL
进程:
R 正在运行中的进程和可运行的进程
可运行的进程是指在等候队列中等CPU资源的进程
fork:
各种情况:
1)子进程先结束,子进程会给父进程发一个SIGCHLD信号,
父进程回收子进程相关资源
2)父进程结束,子进程成为孤儿进程,孤儿进程就会认init(pid=1)为父进程,
子进程结束时,发送SIGCHLD,init回收资源
3)子进程先结束,发送SIGCHLD给父进程,父进程由于某中原因没有收到,
则子进程资源没有回收,子进程变为僵尸进程
孤儿进程是正常情况,僵尸进程应该避免
fork之后会复制数据区,BBS段,堆,栈,文件描述符会共享代码区,文件表
signal:
忽略SIGTERM信号:
signal(SIGTERM, SIG_IGN);
恢复SIGTERM信号的默认行为:
signal(SIGTERM, SIG_DFL);
不能忽略SIGKILL,SIGSTOP信号,也不能捕获
作业:打印孪生素数
用一个进程产生素数,用另外一个进程找孪生素数
孪生素数:
两个数差2 , 3 5, 5 7
进程:
1)shell下执行一个命令,启动一个进程,这个进程就是shell进程的子进程
2)在一个进程用fork函数复制一份自己,复制出的那个进程就是这个进程的子进程,它还会在复制进程的位置开始运行
这两个父子进程唯一的不同是fork函数的返回值不同,
可以根据fork的返回值判断区别父子进程,从而让父子进程执行不同的代码段
fork返回值
>0 父进程 返回的是子进程的pid
==0 子进程
<0 出错
注:fork复制进程是在fork函数运行中复制的,即在函数返回前就已复制结束
3)vfork
和fork一样,创建一个进程,但不会进行复制,主要用于与exec函数搭配使用
还有一个特点:保证子进程先执行
进程关系:
父进程先结束,子进程就会认pid==1(init)为父进程
子进程先结束,发送信号SIGCHLD给父进程,然后回收子进程资源
wait:
父进程等待子进程结束,如果子进程不结束,父进程阻塞
等待子进程的目的是为了获取子进程的返回值
获取返回值:
1)从wait中取得返回信息
int status;
wait(&status);
2)判断是否是正常返回(不是被强行中断)
WIFEXITED(status);
3)取得返回值
WEXITSTATUS(status);
若wait(NULL),表示不关心返回值,只是等待子进程结束
exec系列函数:
基本前缀:exec
可选后缀:
l : list参数为可变长参数
v : vector参数是字符串数组
p : PATH需要用到PATH环境变量中的地址中的程序来执行程序
e : environment传入环境变量
注意:
无论是l,还是v,最后必定要以(char*)0{NULL}结束
exec是将一个程序调入当前进程,覆盖当前进程的所有信息
信号:
进程级别的
信号是一个软件中断技术,信号会打断正常程序的执行
signal:
void (*signal(int, void (*)(int)))(int)
==typedef void(*sighandler_t)(int)
sighandler_t signal(intsigno, sighandler_t handler)
功能类似于:在当前进程上,注册渔歌指定的信号的处理函数,当收到相应的信号时则调用处理函数进程响应处理
sleep函数在休眠期间如果接受到信号被打断,当处理函数返回时不会继续休眠
1.信号
进程可以接受,处理信号
1)信号处理函数,可以处理多个信号,也可以只处理一个
2)首先要先捕获信号
signal函数 简单信号处理,很多动作不能选择
sigaction函数 对信号进行更加精细的处理,有多种选项
sigaction结构 {
sa_handler;//信号处理函数
sa_sigaction;//信号处理函数,当有SA_SIGINFO选项时有效
sa_mask;//要屏蔽的信号
sa_flags;//选项
}
注意:sa_handler和sa_sigaction的实现很可能使用同意存储区,
所以二者只能有其一
3)处理信号 信号会中断正常程序的执行,不可重入函数的调用需注意
1.在正常程序中正在访问全局(静态)变量,而信号处理函数也访问这些变量,
可能出问题
2.正在执行一些系统调用(malloc, free...)在信号函数中也做同样的事,
可能出问题
注意:信号处理函数尽量简单,一般都是处理一些错误,或者处理高优先级的突发情况.
进程处理的信号越多,问题越复杂
4)发送信号
1.键盘按键
Ctrl+C SIGINT
Ctrl+\ SIGQUIT
Ctrl+Z SIGSTOP
...
2.系统错误 系统默认处理一般为终止或忽略
3.shell命令
kill -信号 进程pid
4.进程中发信号
kill函数 kill(进程pid, 信号)
pid>0 给指定进程发
pid==0 给同进程组发
pid==-1 给所有有权限的进程发
pid<-1 给pid的绝对值进程组发
5.alarm
定时发信号SIGALRM
函数不是阻塞函数
如果上一次alarm时间未到,又再次alarm,
则上次alarm取消,返回上回alarm剩余时间
alarm(0)取消定时
6)pause
暂停并等待信号,直到收到一个信号被打断
2.信号集
表示多个信号的变量
3.信号屏蔽
进程在特定阶段,不希望有某些信号打断,可以屏蔽这些信号
1)signal信号处理函数,默认的屏蔽掉同类型的信号
2)sigprocmask函数专门用来屏蔽信号
sigset_t set, oset;
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, &oset);//屏蔽掉所有信号
...//一段不允许任何信号打断的代码
sigprocmask(SIG_SETMASK, &oset, NULL);/恢复原来的屏蔽字
3)sigpending 获取被屏蔽掉的信号
4.进程通信
InterProcess Comunication(IPC)
1)用普通文件 fork
2)管道通信: 有名管道,无名管道, 管道文件是有序文件
3)通过内存映射方式通信
4)用队列方式通信
5)socket文件
6)网络通信
1) 管道
2) FIFO
3) 内存映射
4) Socket
XSI IPC:
1) 消息队列
2) 信号量
3) 共享存储器(最快)
IPC:
管道: 有名(任意两个进程) 无名(父子进程之间)
无名管道:
父进程: int fd[2];
pipe(fd);//创建一个无名管道,两个文件描述符
//fd[0] : 专用于读
//fd[1] : 专用于写
fork子进程
父进程和子进程分别关闭读端或写端(读写个留一个)
父子进程之间开始通信
父子进程分别关闭剩下的读写端
XSI IPC:
共享内存: 消息队列 信号量
共同的特点:
系统内核都会维护IPC结构,进程可以创建IPC结构,创建时需要一个key
产生key的方式:
1)IPC_PRIVATE(默认创建新的IPC结构)
2)在共享的头文件中定义一个key
3)事先给定一个路径和项目号
key_t key = ftok("pathname", num);
shmid_ds msgid_ds semid_ds
编程模型:
1)获得key
2)创建IPC结构(获取IPC结构) shmget msgget semget
3)通信的准备工作shmat
4)通信 读写内存 msgsnd msgrcv
5)释放资源 shmdt
6)删除 shmctl msgctl semctl : IPC_RMID
共享内存:
shmctl(int shmid, int cmd, struct shmid_ds* buf);
cmd:
IPC_STAT : 取得shmid_ds结构的信息
IPC_SET :设置操作系统中的shmid_ds,只允许设置权限信息
IPC_RMID : 删除系统中的shmid_ds
shmid_ds:
struct shmid_ds {
struct ipc_perm shm_perm;
size_t shm_segsz;//内存段大小
pid_t shm_cpid;//创建进程pid
pid_t shm_lpid;//最后挂接或脱接的pid
shmatt_t shm_nattch;//挂接在内存段上的进程数
};
特点:
效率高,但存在同步问题
消息队列:
内核维护的一个链表实现的队列(FIFO)
创建: msgget(key_t, IPC_CREAT | IPC_EXCL | 0600);
返回队列的id
获取id: msgget(key_t, 0);
发送消息:
msgsnd(int msgid, const void* msgp, size_t msgsz, int msgflg);
msgp: 有类型的消息, 无类型的消息
size: 有类型的消息,大小不包括类型本身的长度
无类型的消息,大小就是消息长度
flag: 一般用默认0,阻塞 若设置为IPC_NOWAIT则不阻塞
接受消息:
msgrcv(int msqid,void* msgp,size_t msgsz,long msgtype,intmsgflg);
msgtype==0 : 不判断类型
msgtype>0 :接收消息时要判断类型
msgtype<0 :取小于等于type的绝对值的类型最小的消息(从小到大)
msgflag:
MSG_NOERROR : 如果消息比设定的大小还大,则截断消息,而不出错.
默认出错,消息还留在队列中
IPC_NOWAIT : 如果队列中没有要取的消息,不需要等待,直接返回。
默认是阻塞
线程:
每个线程除拥有自己独立的栈空间、线程ID、一组寄存器值、调度优先级和策略、信号屏蔽字、error变量、线程私有数据外,
其他都是共享的(堆区,全局区,代码区,文件描述符)
进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效(与Windows不同)
三种基本的同步机制:互斥量、读写锁(共享-独占锁)、条件变量(还有信号量)
pthread_join(pthread_t tid, void** value_ptr):
1.让主线程阻塞,直到对应的线程运行结束返回
2.取得线程返回值的地址保存在*value_ptr中
Socket套接字
文件
网络
网络编程:
数据报UPD 一对一
无连接 不安全,可能会丢失数据,数据无序,效率高
数据流TCP C/S
有连接 安全,不会丢失数据,数据有序,效率低
死锁:
产生死锁的原因:
1) 竞争资源
2) 进程间推进顺序非法(请求和释放资源顺序不当)
解决死锁的基本方法:
1) 预防死锁
银行家算法
2) 避免死锁
3) 检测死锁
4) 解除死锁
5) 鸵鸟策略
死锁的4个必要条件
互斥、请求保持、不可剥夺、环路
操作系统中进程调度策略有哪几种?
FCFS(先来先服务),优先级,时间片轮转,多级反馈
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
列举几种进程的同步机制,并比较其优缺点。
在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(BigKernel Lock)、 rwlock、 brlock
(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)