1. gcc编译器
1.1 一个源程序到可执行程序的过程
1)预处理 : gcc -E hello.c -o hello.i
==> hello.i(经过预处理后的源程序文件)
2)生成汇编: gcc -S hello.i ==>hello.s
3)编译 : gcc -c hello.s ==> hello.o
hello.o是二进制文件,按ELF32格式生成。
一定以一个魔数: 7F 'E' 'L' 'F'开头
4)连接 : gcc hello.o
1.2 文件的后缀
.c
.cpp .cc .cxx .C
.m Objective C
.mm Objective C++
.s 汇编
.i c/c++预处理后的源程序
.h 头文件
.o 编译生成的目标文件
.a 静态库文件
.so 共享库文件
2. 环境变量
2.1 是变量,在内存中,变量的值会影响系统或其他程序的运行。
LANG : 系统的语言环境
PATH : 查找要执行程序的路径
JAVA_HOME : Java JDK安装目录
ORACLE_HOME : Oracle数据库的安装目录
ORACLE_SID : Oracle实例名
....
在bash下修改环境变量的方式:
export 变量名=变量值 (注意=号左右没空格)
2.2 和gcc相关的环境变量
CPATH/C_INCLUDE_PATH
: 设置头文件的搜索路径
程序中,使用#include <头文件>,gcc除在系统指定的位置查找头文件之外,还会在这个环境变量指定的位置查找头文件。
也可以用以下办法指定头文件的位置:
gcc -c xxxx.c -I 头文件所在路径
3.静态库
3.1 库的概念(library)
将多个目标文件打包到一起,形成一个文件,叫库。
3.2 创建静态库
1)写源程序 : mymath.c view.c
2)编译 :
gcc -c mymath.c ==> mymath.o
gcc -c view.c ==> view.o
3)生成静态库(打包)
ar -r libmyku.a mymath.o view.o
4)提供头文件 : mymath.h
3.3 使用静态库
1)写调用源程序 : test.c
2)编译源程序 :
gcc -c test.c ==> test.o
3)连接
第一种方式: gcc test.o libmyku.a
==> a.out
第二种方式:
配置环境变量LIBRARY_PATH
export LIBRARY_PATH=库文件所在的路径
gcc test.o -l myku(库名)
==> a.out
4.共享对象库(shared object library)
简称为“共享库”, 也可以叫动态连接库(windows)
4.1 创建共享库
1)写源程序 : mymath.c view.c
2)编译源程序 :
gcc -c -fpic mymath.c ==> mymath.o
gcc -c -fpic view.c ==> view.o
3)生成共享库文件
gcc -shared mymath.o view.o
-o libmyku.so
4)提供头文件
4.2 使用共享库
1)写调用库函数的源程序 : test.c
2)编译 : gcc -c test.c ==> test.o
3)连接 : 和连接静态库完全一样
gcc test.o libmyku.so
或: gcc test.o -l myku
(需要环境变量LIBRARY_PATH)
==> a.out
4)运行
a.out
执行前,需要将共享库文件所在的路径配置到环境变量LD_LIBRARY_PATH中,否则无法执行。
5.静态库和共享库区别
5.1 静态库本质是一个打包文件,里面包含很多.o文件,共享库本质上是一个可以执行的文件,但没有程序入口
5.2 连接到静态库时,程序调用的函数会直接拷贝到a.out中,所以a.out运行期间不需要静态库文件做支持。
连接到共享库时,程序调用的函数不会拷贝到a.out中,所以a.out运行期间需要共享库文件做支持。
5.3 实际开发中会大量使用共享库,很少用静态库。
共享库比较灵活,可以大量减少内存的消耗。当库文件本身很小时,可以考虑使用静态库
6.程序中主动加载并调用共享库中的函数
#include <dlfcn.h>
1)打开共享库文件
2)获取调用函数
3)调用函数
4)关闭库文件
编译时:
gcc xxx.c -ldl
类Unix操作系统 Unix/Linux
静态库 libxxxx.a
共享库 libxxxx.so
Windows系统
静态库 xxxx.lib
动态连接库 xxxx.dll
书:
Unix环境高级编程 第2版
Linux程序设计
Richard Steven:
Unix进程间通信 第2版
Unix网络编程 第3版
TCP/IP 详解(1,2,3)
1.错误处理
如果一个函数调用失败,错误如何处理(使用返回值):
1)如果函数在正确的情况下,不可能返回负数,那么以返回-1代表函数出错。
2)如果函数返回的是指针,以返回空指针代表函数出错。
FILE* file = fopen("a.txt", "r");
if(file==NULL) 失败
3)函数不会返回值,或由参数返回函数结果,那么函数返回0代表成功执行,返回-1代表失败。
4)如果函数返回是void,一般代表函数不可能失败
2.用errno获取错误原因
2.1 在头文件errno.h中定义了一个全局变量: int errno; 用来保存最近一次函数调用失败的原因。
2.2 由于errno的值会随时发生改变,所以当发现函数调用失败时,要马上从errno中取得失败原因,
否则很有可能被其他情况所覆盖
2.3 不能用errno的值判断函数调用是否成功
3.环境表
程序: 可以执行的文件,存在磁盘上
进程: 正在运行的程序,存在内存中
当shell启动进程时,会将一个环境表的地址传入进程,这个环境表保存的是进程的可以读取的所有的环境变量。
4.内存管理
4.1 分配内存的一些方式:
STL ------> 自动分配,自动释放
|
C++ ------> new/delete
|
C语言 ----> malloc/free
| 平台无关
-------------------------------------------
| 平台相关
类Unix操作系统 ----> brk/sbrk
|
低层内存分配 ----> mmap/munmap
| 用户级
--------------------------------------------
| 内核级
内核级内存分配 --->kmalloc,vmalloc
|
....
|
get_free_page();
4.2 C语言和C++语言的内存分配情况:
new/delete 有什么区别:
4.3 进程空间的组成:
当一个程序被调入内存中执行时,会变成一个进程,此进程的空间被划分成以下几个区域:
1)代码区:
代码区是只读的,程序代码会被读入此区域,程序执行期间执行的就是代码区中的代码。
2)全局区
除了代码区,其他区域空间都是可读可写的。全局用来给全局变量分配空间。
全局数据区,数据区,全局静态区...
3)BSS段
紧接着全局区,给没有初始值全局变量分配空间。当进行开始运行之间,BSS段会被清零一次,
所以,没有初始的全局变量,它的初始值是0
4)堆区
用malloc,new,...分配的空间
也叫自由区,想什么时候分配空间就什么时候分配,想什么时候释放就什么时候释放。
5)栈区
给局部变量(自动变量)分配空间的地方
局部变量的空间分配和释放都是自动的,用户无法干涉。
1.进程的内存空间
正文区(text) : 只读,有执行权限
数据区(data) : 全局变量
BSS : 未初始化的全局变量,执行前清零
堆区(heap) : 自由区
栈区(stack) : 放局部变量,分配和释放是自动的
2. 虚拟内存地址
每个进程都有4G的虚拟内存地址,当进程需要真正的物理内存空间时,向系统申请,
系统会分配给进程相应的物理内存空间,系统会将进程的一部分虚拟内存地址和
分配好的物理内存空间一一对应起来,这个过程叫内存映射。
3.产生段错误的原因
3.1 当程序访问或修改没有权限访问或修改的内存空间时,会产生段错误
3.2 当使用一个虚拟内存地址时,这个地址并没有和真正的物理内存空间对应(映射),也会产生段错误。
4.堆中分配空间
当用malloc在堆里分配空间时,malloc会在分配的空间后加一个结束标记,
所以使用malloc分配的空间时一定不要越界访问,否则会破坏malloc后台维护的数据结构,导致内存空间无法释放等问题。
malloc比较复杂,它会在后台维护一个双向链表的数据结构来管理分配好的空间。
5.分配物理空间
操作系统分配物理内存空间时,会以页为单位,一般一页为4096个字节。
系统对malloc会格外照顾,第一次malloc时,我们的系统会给malloc分配(映射)33个页面的物理内存空间,
以后的malloc就从这33个页面中获取要使用的空间。
6.类Unix操作系统分配内存的函数brk/sbrk
6.1 void* sbrk(int sz);
sz>0 : 将当前位置向后移sz个字节,相当于分配内存空间
sz<0 : 将当前位置向前移sz个字节,相当于释放内存空间
sz==0 : 用来获取当前位置
sbrk的返回值永远是移动之前的位置。
6.2 int brk(void* p);
将当前位置移动到指定位置p处,如果p在当前的位置后面,相当于分配内存空间,否则就相当于释放内存空间。
返回0代表成功,-1代表失败
6.3
sbrk和brk的本质是一样,都是在移动当前位置,所以可以混用。实际中,经常用sbrk分配空间,用brk释放空间。
7. mmap
memory mapping(内存映射)
#include <sys/mman.h>
void* mmap(void* start, size_t length,
int prot, int flags, int fd,
off_t offset);
1)start, 没有映射过的虚拟内存地址
sbrk(0)可以获取一个没有映射过的虚拟内存地址。
可以给start传NULL, 系统会自动找一个没用过的虚拟内存地址进行映射。
一般都传NULL.
2) length : 要映射多大的物理空间
此参数应该以页面的倍数传。如果不是页面的倍数,系统自动按页对齐。
3) int prot : 映射好的内存空间的访问权限
PROT_READ : 读权限 (1)
PROT_WRITE : 写权限 (2)
PROT_EXEC : 执行权限 (4)
可以有几个权限 : PROT_READ | PROT_WRITE
4) int flags : 标记
在映射时不同的选项
MAP_SHARED/MAP_PRIVATE 两个必选其一
MAP_SHARED : 以共享的方式映射
MAP_PRIVATE : 以私有的方式映射
MAP_ANONYMOUS :
匿名映射(专门用来映射内存)
可以几个合并使用:
MAP_PRIVATE | MAP_ANONYMOUS
5) int fd : 文件描述符
仅用于映射到文件,当第四个参数中有MAP_ANONYMOUS时,此参数无效
6) off_t offset : 文件偏移量
仅用于映射到文件
返回值: void*
映射好的内存空间的首地址。
如果映射失败,此函数返回-1
if(mmap(...)==(void*)-1){//失败}
#define MAP_FAILED (void*)-1
if(mmap(...)==MAP_FAILED){//失败}
8.系统调用
操作系统内核给用户程序提供的API
系统调用函数运行在系统态,普通的程序代码运行在用户态。
1.文件
一切都是文件
/dev/tty 终端设备文件(键盘和显示器)
/dev/null 空设备文件(Unix黑洞)
typedef int ssize_t;
typedef unsigned int size_t;
....
off_t
2.打开文件 open
flags:
以下三个必选其一:
O_RDONLY 以只读方式打开
O_WRONLY 只写
O_RDWR 读写
如果是创建文件,标记中加入以下内容:
O_CREAT 创建文件时必须有的
O_EXCL 如果文件已经存在,由失败,返回-1
O_TRUNC 如果文件存在,清空文件
创建文件时必须传第三个参数(文件的访问权限)
O_APPEND : 打开文件后,以追加的方式写文件 uf_end
只要标记之间不冲突,就可以联合使用,如:
O_RDWR | O_CREAT | O_TRUNC
3.read/write
read/write函数都是系统调用函数,运行在系统态,所以,实际开发中,
尽量不要频繁调用这此函数,如果要频繁地输入输出大量数据,应该尽量使用缓冲区,
以减少调用read/write的次数,从而提高效率。
4.文件偏移量
什么时候文件偏移量会移动:
1)lseek函数可以主动移动文件偏移量
2)read会移动文件偏移量
3)write会移动文件偏移量
5.文件加锁
5.1 文件锁的类型:
读锁 : 共享锁, 如果A进程对文件的某个区域加了读锁,
B进程也可以对文件的此区域加读锁,但是不能对此区域加写锁。
写锁 : 独占锁(排他锁), 如果A进程对文件的某个区域加了写锁,
B进程就不能对此区域加写锁, 也不能对此区域加读锁
1. F_GETLK的使用
测试某一个指定的锁是否可以加到文件上。
当调用fcntl(fd, F_GETLK, &lock);之后,如果lock中的值没有发生任何变化(除l_type变成F_UNLCK),
说明锁lock可以加到文件上。
如果测试锁不能加到文件上,那么内核会将导致加不上锁的原因锁保存到测试锁中,
重要的是,原因锁中的l_pid中保存的是另外一个进程(已经加了锁的进程)的进程ID.
2.当文件关闭或进程结束时,文件锁会自动释放。
3.stat
4.文件映射
mmap
struct s{
int a;
int b;
};
struct s e = {1,2}; C89
struct s e = {.a = 1, .b = 2} C99
struct s e = { a : 1, b : 2} GUN C
1.目录操作
1.1 操作目录
mkdir : 创建目录
rmdir : 删除目录(只能删除空目录)
chdir : 改变当前工作目录(进入目录)
getcwd : 返回当前工作目录
1.2 读目录内容
#include <dirent.h>
directory entry : 目录项
打开目录
DIR* opendir(char* name);
读取已打开目录的其中一个目录项
struct dirent* readdir(DIR* dir);
目录项的类型 d_type :
1 : 管道文件
4 : 目录
8 : 普通文件
10 : 软连接文件
12 : Socket文件
2.进程
Solaris进程状态:
S : sleep睡眠状态
O : 可运行状态
T : 挂起状态(阻塞)
R : 运行状态
Z : 僵尸(僵死)进程
进程之间的父子关系:
1) A进程启动了B进程,A就是B的父进程。启动之后父子进程同时运行。如果子进程B先结束,子进程会通知父进程A,父进程会回收子进程的相关资源。
2)父进程启动子进程后同时运行,父进程先结束运行,子进程成为孤儿进程,子进程马上认init为父进程。进程1(init)一般认为孤儿院。
3)父子进程同时运行,子进程先结束,通知父进程,父进程未响应,子进程结束了。父进程的进程表项中依然记录着一个子进程,这个子进程就成了僵尸进程。
3.操作进程
proccess descriptor
getpid, getppid, getuid, geteuid
fork
4.退出进程
4.1 正常退出
在main函数中return,或在进程任何位置调用exit都会导致进程正常结束。进程正常结束前,会自动调用atexit注册在进程上的一些函数,进程的缓冲区也会被清空...
4.2 立即退出
当进程调用了下面两个函数的其中一个时,进程会立即结束,atexit注册的函数不会调用,进程的缓冲区也不会清空...
#include <stdlib>
_Exit(int); //标准C语言版本
或 #include <unistd.h>
_exit(int); //标准Unix版本
1.信号(signal)
信号是一种中断。中断是系统调度一种常见方式。
具体说就是暂停当前任务,来处理更重要的任务。
信号是软件中断。是进程之间的通信技术之一。
信号一般处理的都是异常情况,如:
CTRL+C会产生信号: 直接终止
CTRL+\会产生信号: 退出
内存访问异常,产生信号 : 段错误
文件访问异常,产生信号 : 总路错误
错误的数学运算除零 : 浮点数例外
信号本质上是一个整数,这此整数几乎都有一个名字,这个名字被定义成了宏:
#define SIGINT 2
2.信号的分类
不可靠信号,不支持排队,容易丢失。(1~32)
可靠信号,支持排队,不会丢失。(33~64)
3.信号是如何产生的
3.1 访问资源出现
访问内存出现段错误
文件出现总路错误
除零出现浮点数例外
....
3.2 用户中断
CTRL+C, CTRL+\
3.3 进程调用一些函数发送的信号
kill函数
alarm函数
raise函数
sigqueue函数
setitimer函数
......
3.4 shell发出的信号(命令)
kill命令
4.进程如何处理信号:
4.1 默认,当信号来时,采取默认动作
不同的信号默认动作不一样,绝大部分信号的默认处理动作是退出进程。
4.2 忽略,当信号来时,不做任何处理
4.3 捕获
当信号到来时,捕获信号并调用指定函数处理。
5.注册信号处理函数
typedef void (*sighandler_t)(int);
sighandler signal(int sig,sighandler_t h);
signal函数的本质:
void (*signal(int s,void(*f)(int)))int();
返回值:
如果安装(注册)信号处理器,安装(注册)成功,signal函数返回处理器(信号处理函数)本身。如果失败,返回-1。 (void(*)(int))-1;
#define SIG_ERR (void(*)(int))-1
if(signal(3, fa)==SIG_ERR){
perror("安装失败"),exit(-1);
}
6.子进程的信号问题
6.1 子进程会继承父进程的信号处理方式。
6.2 但是,如果子进程是调用了exec函数的,那么父进程中忽略的信号处理方式会被子进程继承,捕获并调用函数处理的信号会被子进程恢复成默认行为。
7.发送信号的函数
7.1 kill函数
int kill(pid_t pid, int signo);
pid:
>0 : 给pid进程发送信号signo
==0 : 给当前进程组的所有进程发signo
==-1 : 给有权限发送的所有进程发signo
<-1 : 给指定进程组(pid的绝对值)发signo
signo :
要发送的信号
0 : 并不真正的发送信号,只是用来测试能否则给进程发送,如果可以发,函数返回0
返回值: 0 : 发送成功
-1 : 发送失败
7.2 raise函数
只是给自己(当前进程)发信号,相当于:
kill(getpid(), sig);
7.3 alarm函数
类似计数器
7.4 setitimer 计数器
7.5 sigqueue
8. sleep
是一个不可重入函数,如malloc也是不可重入函数。
pause
阻塞当前进程,等信号到来。信号到来后,进程先处理信号(默认,忽略,调用函数),处理完后pause函数会返回-1
9.信号屏蔽
9.1 信号集
信号集是一个变量,此变量可以表示多个信号。信号集变量的类型是sigset_t
9.2 设置进程信号屏蔽字
int sigprocmask(int how, sigset_t*new,sigset_t* old);
new : 要设置的新的信号屏蔽字
old : 将以前的信号屏蔽字保存到此变量中
how:
SIG_BLOCK 将新的信号屏蔽字填加到当前信号屏蔽字中。
1 2 3 + 3 4 5 = 1 2 3 4 5
SIG_UNBLOCK 从当前屏蔽字中删除指定的信号集
1 2 3 + 3 4 5 = 1 2
SIG_SETMASK 设置当前信号屏蔽为new
1 2 3 + 3 4 5 = 3 4 5
10. sigaction函数
10.1 函数功能:
安装(注册)一个信号处理器,和 signal是一样的,但功能比signal多一点。
10.2 struct sigaction
struct sigaction{
sa_handler : 一般情况使用信号处理函数
sa_sigaction : 需要信号的详细信息时使用的信号处理函数
sa_mask : 正在调用信号处理函数期间自动屏蔽掉的信号集
flags : 标记
};
flags:
SA_RESETHAND/SA_ONESHOT :
当执行完第一次信号处理函数后,将此信号的处理方式恢复成默认行为
SA_NODEFER/SA_NOMASK :
当正在处理一个信号时,如果此信号现次到,马上处理新的信号。
SA_SIGINFO :
信号处理函数将使用sa_sigaction,并且信号处理函数可以获取此信号相关的一些信息,如:发送者,信号携带的数据等....
1.进程组
将多个进程编组,可以形成进程组。多个进程放入一个组可以统一对这些进程进行操作。
setpgrp(); //设置进程组
setpgid(pid_t pid, pid_t gpid); //将进程放入进程组
kill(-pid, 2);//组指定的进程组发信号
2. 发送信号函数sigqueue
此函数在发送信号的同时,可以携带一个数据。
联合 union
union{
int x;
char cs[4];
};
x=0x41424344;
cout << cs << endl;//DCBA
3.计时器
每一个进程都有三个计时器(由内核维护):
真实计时器(Real iTimer)
程序运行总时间(用户态+系统态+睡眠)
虚拟计时器(Virtual iTimer)
用户态时间
实用计时器(Prof iTimer)
用户态+内核(系统)态
4.进程间通信
Inter Process communication(IPC)
管道
XSI IPC :
共享存储段
消息队列
信号量集
Socket通信
5.管道通信
5.1 创建管道文件
5.1.1 用命令mkfifo创建
5.1.2 在程序中用函数mkfifo创建
5.2 用读写文件方式进行通信
1)打开管道文件
2)以读写文件的方式通信
3)关闭文件
4)删除管道文件
5.3 无名管道
不必在磁盘上创建管道文件,依赖的管道文件在内存中自动创建:
pipe 函数会自动创建管道文件并打开。
无名管道只能用于父子进程之间的通信。
6. 共享存储段
6.1 生成key
6.2 创建共享存储段
6.3 获取已有的共享存储段的ID
6.4 挂接(映射)到共享存储段
6.5 脱接(解除映射)共享存储段
6.5 shmctl函数
IPC_STAT
IPC_SET
IPC_RMID
进程间通信
管道
XSI IPC
1. XSI IPC通信的共性:
共享存储段,消息队列,信号量集
1.1 必需有key,key是创建和获取IPC通信结构的唯一依据。
1.2 生成key:
1)直接使用IPC_PRIVATE做为key.这种办法很少很用,因为IPC_PRIVATE只能在创建IPC结构时使用。
2)在共享头文件中直接定义key的值。
3)调用ftok函数生成key
ftok函数要求路径是真实的。
当前目录是:/home/soft01/unixc/day09
key1 = ftok(".", 100);
key2 = ftok("/home/soft01/unixc/day09",100);
key1 == key2 ?
1.3 都需要IPC结构通信。
IPC结构是内核创建并管理的一个通信媒介。
1.4 创建和获得ID都是同一个函数,创建时都需要指定标志:IPC_CREAT,如果想创建的IPC结构已经存在,那么IPC_EXCL就会起作用。另外,标志中一定要有权限:
IPC_CREAT|IPC_EXCL|0600
1.5都有一个控制函数
shmctl, msgctl, semctl
这此函数有共同的功能:
IPC_STAT : 用来获取IPC结构的属性,并保存到指定的结构中。
IPC_SET : 用来修改权限
IPC_RMID : 用来删除IPC结构
2.消息队列
2.1消息的分类
无类型消息:
任何类型的数据都可以做为消息发送和接收
有类型消息:
有类型消息一定是一个结构体,而且结构的第一个成员一定是long类型的成员,表示消息的类型:
struct msg{
lont mtype;//类型
//..消息的数据
};
2.2 无类型的消息
发送无类型消息时,直接发送就可以了。
1.信号量集(Semaphore Array)
集中可以有多个计数器。可以对多个资源进行保护,保护指限制访问这此资源的进程数量。
1.1 使用信号量集:
1)生成key
2)创建信号量集
3)初始化信号量集
4)使用信号量保护资源
2. 网络通信
Socket通信
2.1 Socket
插座,套接字
在Unix网络通信中,socket可以认为是一个文件,这个文件可以socket文件,也可以是网络。专门有socket描述符来表示一个socket.
2.2 IP
在网络中唯一标识一台计算机
192.168.182.53
255.255.255.0
-----------------------
192.168.182.0 网络地址
53 主机地址
2.3通信模型
1)创建一个socket
2)准备通信地址
3)绑定
4)通信
5)关闭socket
2.4 socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain : 域, 用来选择通信使用的协议簇(protocol family)。
PF_UNIX,PF_LOCAL,PF_FILE
AF_UNIX,AF_LOCAL,AF_FILE : 本地通信
PF_INET,AF_INET : 网络通信(TCP/IP) IPv4
PF_INET6,AF_INET6:网络通信(TCP/IP) IPv6
PF_IPX : Novell通信协议
PF_PACKET : 内核通信协议
type : 通信类型
SOCK_STREAM : 以数据流的方式通信
SOCK_DGRAM : 以数据报的方式通信
protocol : 通信协议
一般情况下是无效的。经常传0
前两个参数基本上可以确定通信协议。
返回值:
socket描述符(类似于文件描述符)
返回-1,说明创建socket失败
2.5准备通信地址
#include <sys/socket.h>
struct sockaddr{
sa_family;//协议簇
sa_data;//地址
};
本地通信地址:
#include <sys/un.h>
struct sockaddr_un{
int sun_family;//协议簇
char sun_path[];//socket文件路径
};
TCP/IP通信地址:
struct sockaddr_in{
sin_family;//协议簇00010010
short sin_port;//端口号
sin_addr;//IP地址
};
2.6 端口号
用来定位一个进程的整数值。IP可以定位一台网络上的计算机,端口号可以定位这个计算机上的一个进程。
是一个unsigned short类型的整数,范围0~65535,逻辑上一个端口号代表一个进程。
默认端口号:
http: 80
www.tarena.com.cn ==> http://www.tarena.com.cn:80 ==> http://212.34.12.89:80 ==> 服务进程(web服务) ==>html==>回到浏览==>解析并显示html页面。
ftp : 21
telnet : 23
0~1024之间的端口号一般都被系统使用。
网络字节序和本地字节序的转换:
htons
htonl
ntohs
ntohl
2.7 IP地址
2.7.1 IP地址的表示方式:
1)点分十进制
192.168.182.53
2)十六进制(二进制)
C0A8B635
3)整数 3232282165
2.7.2 IP地址转换:
inet_addr : 点分十进制=>整数
3.TCP通信
一对一 一对多(C/S) Client/Server
面向连接,可靠的 传输协议
3.1 Server:
1)创建socket
2)准备地址
3)绑定
4)监听
listen(int sockfd, int len);
让服务最多准备多长的队列缓存申请连接的客户端。
5)等待客户连接
int accept(int sockfd, struct sockaddr*addr, sockaddr_t* len);
addr: 用来客户端的地址
len: 必需有值(输入输出参数)
返回值:成功后,会返回一个socket,此socket已经和客户端建立了连接关系,服务要和客户端通信,就使用此socket
6)和客户端通信
7)关闭
3.2 客户端
4.UDP通信
无连接的,不可靠的 传输协议
1.UDP通信
1.1 server
1)创建socket
2)准备地址
3)绑定
4)通信
5)关闭
1.2 client
1)创建socket
2)准备服务器端地址
3)连接(可以连接,也可以不连接)
4)通信
5)关闭
2.多线程
2.1线程
执行中的一段程序序列。
和进程的区别:
1)线程是进程一部分,属于进程
2)进程是资源分配的基本单位,拥有自己独立的内存空间。
3)线程没有自己独立的内存空间,进程中的多个线程共享共享的所有资源(内存...)
4)启动一个进程需要消耗更多的系统资源和系统时间,而启动一个线程需要的资源和时间较少。
进程是重量级的,线程是轻量级的。
2.2 启动线程
#include <pthread.h> ==> libpthread.so
POSIX规范中规定的多线程整个方案。
int pthread_create(
pthread_t* id,//线程ID
pthread_attr_t* attr,//线程属性
void*(*f)(void*),//线程任务
void* p);//任务参数
id : 输出参数,返回线程id
attr : 用来设置线程的属性,NULL表示默认
f : 函数表示线程的任务,线程启动后会自动调用此函数
p : 给f函数传的参数
返回值:
成功返回0,失败返回错误原因(错误代码)
多线程函数大部分都如此设计返回值。
2.3 主线程和普通线程
当线程成功创建后,直接开始运行。main函数是主线程,一个进程中只要主线程结束,进程就会退出,进程退出时,进程中的线程直接消失。
进程中,多个线程在一个时间段内是并发(同时)的运行, 但在一个时间点,只有一个线程运行(CPU只有一个)。
2.4 多线程作用:
提高程序运行效率。由多个线程同时执行多个任务。
2.5 给线程函数传参数
2.6 线程函数的返回值
只能返回全局变量的地址或静态变量的地址等,总之,返回的地址指向的空间不能随线程的结束而释放。
3.线程函数介绍
3.1 返回当前线程的ID:
pthread_self
3.2 判断两个线程是否是同一个
pthread_equal(pthread_t id1,pthread_t id2);
3.3 线程终止
1)线程函数结束,线程终止。
2)线程函数中调用pthread_exit,线程终止
3)被别的线程终止,或自身出问题
3.4 线程分离
处理分离状态的线程不能被join
pthread_detach
3.5 线程取消
一个线程可以取消另外一个线程的运行,前提是被取消的线程允许取消。一个线程默认是允许被取消的。
pthread_cancel :取消指定线程
可以让线程不被取消:
pthread_setcancelstate(int , int*);
PTHREAD_CANCEL_ENABLE : 打开取消
PTHREAD_CANCEL_DISABLE : 关闭取消
设置取消类型:
pthread_setcanceltype(int, int*);
PTHREAD_CANCEL_DEFERRED : 取消点取消
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消
4.线程属性
1)定义属性变量
pthread_attr_t attr;
2)初始化变量
pthread_attr_init
3)设置具体的属性
pthread_attr_setxxxxxxx
4)启动线程
pthread_create(&id, &attr, ....);
5)销毁属性变量
pthread_destroy(&attr)
5.线程同步
一个进程中的多个线程是共享进程中的相关资源的,那么,在多个线程同时对同一个数据进行访问(读写)时,
会造成数据的不完整和不一致。为了避免这种问题,我们就需要对多个线程进行同步协调。
5.1 互斥量(互斥锁)
5.2 信号量
5.3 条件变量
附:
restrict:
C99新增语法,此关键字只能出现在函数的指针参数上。主要作用是提高此指针参数在函数中的访问效率。
struct Data{
int x;
int y;
char name[];
//...
};
void fa(struct Data*restrict d){
for(int i=0; i<1000000; i++){
d->x
d->y
d->name
}
}
void fa(struct Data d){
for(int i=0; i<1000000; i++){
d.x
d.y
d.name
}
}
线程:
1)相关函数调用
2)理解 并发运行
1.线程并发访问临界资源
2.互斥锁(互斥量) mutex
是一个变量,代表一把锁。独占锁,排它锁
1)定义一个互斥量(变量)
类型: pthred_mutex_t
pthread_mutex_t lock;
2)初始化互斥量
pthread_mutex_init
或 :
lock = PTHREAD_MUTEX_INITIALIZER;
3)加锁
pthread_mutex_lock(&lock);
4)解锁
pthread_mutex_unlock(&lock);
5)销毁
pthread_mutex_destroy(&lock);
是变量,当计数器用
#include <semaphore.h>
1)定义信号量(变量)
sem_t sem;
2)初始化信号量值(给计数器赋初始值)
sem_init(sem_t* sem, int ps,int value);
sem: 要初始化的信号量
ps :
0 : 线程间共享的信号量
非0 :进程间共享的信号量
value :信号量的初始值
3)获取一个信号量(减1)
sem_wait(&sem)
当信号量的初始值为1时相当于加锁
4)释放一个信号量(加1)
sem_post(&sem)
当信号量值为0时相当于解锁
5)销毁
sem_destroy(&sem)
两种用法:
1)初始化为1,当锁来用,相当于互斥量
2)初始化为一个整数,用于限制同时访问某资源的线程数,相当计数器。
5.条件变量
5.1 生产者和消费者问题
数据 IT
5.2 使用条件变量,可以让当前线程阻塞
当某个条件成立时,当前线程已经无法继续运行,让当前线程主动进入阻塞状态。
1)是一个变量,类型pthread_cond_t
pthread_cond_t full;
2)初始化条件变量
pthread_cond_init函数
或 full = PTHREAD_COND_INITIALIZER;
3)当条件成立,让当前线程阻塞:
pthread_cond_wait(
pthread_cond_t*cond,
pthread_mutex_t*mutex);
cond: 让当前线程阻塞在此变量中。
mutex: 线程阻塞前释放该锁
4)其他线程唤醒被阻塞在条件变量中的线程
唤醒一个:
pthread_cond_signal(pthread_cond_t*);
唤醒所有:
pthread_cond_broadcast(pthread_cond_t*);
5)销毁
pthread_cond_destroy
1.1 一个源程序到可执行程序的过程
1)预处理 : gcc -E hello.c -o hello.i
==> hello.i(经过预处理后的源程序文件)
2)生成汇编: gcc -S hello.i ==>hello.s
3)编译 : gcc -c hello.s ==> hello.o
hello.o是二进制文件,按ELF32格式生成。
一定以一个魔数: 7F 'E' 'L' 'F'开头
4)连接 : gcc hello.o
1.2 文件的后缀
.c
.cpp .cc .cxx .C
.m Objective C
.mm Objective C++
.s 汇编
.i c/c++预处理后的源程序
.h 头文件
.o 编译生成的目标文件
.a 静态库文件
.so 共享库文件
2. 环境变量
2.1 是变量,在内存中,变量的值会影响系统或其他程序的运行。
LANG : 系统的语言环境
PATH : 查找要执行程序的路径
JAVA_HOME : Java JDK安装目录
ORACLE_HOME : Oracle数据库的安装目录
ORACLE_SID : Oracle实例名
....
在bash下修改环境变量的方式:
export 变量名=变量值 (注意=号左右没空格)
2.2 和gcc相关的环境变量
CPATH/C_INCLUDE_PATH
: 设置头文件的搜索路径
程序中,使用#include <头文件>,gcc除在系统指定的位置查找头文件之外,还会在这个环境变量指定的位置查找头文件。
也可以用以下办法指定头文件的位置:
gcc -c xxxx.c -I 头文件所在路径
3.静态库
3.1 库的概念(library)
将多个目标文件打包到一起,形成一个文件,叫库。
3.2 创建静态库
1)写源程序 : mymath.c view.c
2)编译 :
gcc -c mymath.c ==> mymath.o
gcc -c view.c ==> view.o
3)生成静态库(打包)
ar -r libmyku.a mymath.o view.o
4)提供头文件 : mymath.h
3.3 使用静态库
1)写调用源程序 : test.c
2)编译源程序 :
gcc -c test.c ==> test.o
3)连接
第一种方式: gcc test.o libmyku.a
==> a.out
第二种方式:
配置环境变量LIBRARY_PATH
export LIBRARY_PATH=库文件所在的路径
gcc test.o -l myku(库名)
==> a.out
4.共享对象库(shared object library)
简称为“共享库”, 也可以叫动态连接库(windows)
4.1 创建共享库
1)写源程序 : mymath.c view.c
2)编译源程序 :
gcc -c -fpic mymath.c ==> mymath.o
gcc -c -fpic view.c ==> view.o
3)生成共享库文件
gcc -shared mymath.o view.o
-o libmyku.so
4)提供头文件
4.2 使用共享库
1)写调用库函数的源程序 : test.c
2)编译 : gcc -c test.c ==> test.o
3)连接 : 和连接静态库完全一样
gcc test.o libmyku.so
或: gcc test.o -l myku
(需要环境变量LIBRARY_PATH)
==> a.out
4)运行
a.out
执行前,需要将共享库文件所在的路径配置到环境变量LD_LIBRARY_PATH中,否则无法执行。
5.静态库和共享库区别
5.1 静态库本质是一个打包文件,里面包含很多.o文件,共享库本质上是一个可以执行的文件,但没有程序入口
5.2 连接到静态库时,程序调用的函数会直接拷贝到a.out中,所以a.out运行期间不需要静态库文件做支持。
连接到共享库时,程序调用的函数不会拷贝到a.out中,所以a.out运行期间需要共享库文件做支持。
5.3 实际开发中会大量使用共享库,很少用静态库。
共享库比较灵活,可以大量减少内存的消耗。当库文件本身很小时,可以考虑使用静态库
6.程序中主动加载并调用共享库中的函数
#include <dlfcn.h>
1)打开共享库文件
2)获取调用函数
3)调用函数
4)关闭库文件
编译时:
gcc xxx.c -ldl
类Unix操作系统 Unix/Linux
静态库 libxxxx.a
共享库 libxxxx.so
Windows系统
静态库 xxxx.lib
动态连接库 xxxx.dll
书:
Unix环境高级编程 第2版
Linux程序设计
Richard Steven:
Unix进程间通信 第2版
Unix网络编程 第3版
TCP/IP 详解(1,2,3)
1.错误处理
如果一个函数调用失败,错误如何处理(使用返回值):
1)如果函数在正确的情况下,不可能返回负数,那么以返回-1代表函数出错。
2)如果函数返回的是指针,以返回空指针代表函数出错。
FILE* file = fopen("a.txt", "r");
if(file==NULL) 失败
3)函数不会返回值,或由参数返回函数结果,那么函数返回0代表成功执行,返回-1代表失败。
4)如果函数返回是void,一般代表函数不可能失败
2.用errno获取错误原因
2.1 在头文件errno.h中定义了一个全局变量: int errno; 用来保存最近一次函数调用失败的原因。
2.2 由于errno的值会随时发生改变,所以当发现函数调用失败时,要马上从errno中取得失败原因,
否则很有可能被其他情况所覆盖
2.3 不能用errno的值判断函数调用是否成功
3.环境表
程序: 可以执行的文件,存在磁盘上
进程: 正在运行的程序,存在内存中
当shell启动进程时,会将一个环境表的地址传入进程,这个环境表保存的是进程的可以读取的所有的环境变量。
4.内存管理
4.1 分配内存的一些方式:
STL ------> 自动分配,自动释放
|
C++ ------> new/delete
|
C语言 ----> malloc/free
| 平台无关
-------------------------------------------
| 平台相关
类Unix操作系统 ----> brk/sbrk
|
低层内存分配 ----> mmap/munmap
| 用户级
--------------------------------------------
| 内核级
内核级内存分配 --->kmalloc,vmalloc
|
....
|
get_free_page();
4.2 C语言和C++语言的内存分配情况:
new/delete 有什么区别:
4.3 进程空间的组成:
当一个程序被调入内存中执行时,会变成一个进程,此进程的空间被划分成以下几个区域:
1)代码区:
代码区是只读的,程序代码会被读入此区域,程序执行期间执行的就是代码区中的代码。
2)全局区
除了代码区,其他区域空间都是可读可写的。全局用来给全局变量分配空间。
全局数据区,数据区,全局静态区...
3)BSS段
紧接着全局区,给没有初始值全局变量分配空间。当进行开始运行之间,BSS段会被清零一次,
所以,没有初始的全局变量,它的初始值是0
4)堆区
用malloc,new,...分配的空间
也叫自由区,想什么时候分配空间就什么时候分配,想什么时候释放就什么时候释放。
5)栈区
给局部变量(自动变量)分配空间的地方
局部变量的空间分配和释放都是自动的,用户无法干涉。
1.进程的内存空间
正文区(text) : 只读,有执行权限
数据区(data) : 全局变量
BSS : 未初始化的全局变量,执行前清零
堆区(heap) : 自由区
栈区(stack) : 放局部变量,分配和释放是自动的
2. 虚拟内存地址
每个进程都有4G的虚拟内存地址,当进程需要真正的物理内存空间时,向系统申请,
系统会分配给进程相应的物理内存空间,系统会将进程的一部分虚拟内存地址和
分配好的物理内存空间一一对应起来,这个过程叫内存映射。
3.产生段错误的原因
3.1 当程序访问或修改没有权限访问或修改的内存空间时,会产生段错误
3.2 当使用一个虚拟内存地址时,这个地址并没有和真正的物理内存空间对应(映射),也会产生段错误。
4.堆中分配空间
当用malloc在堆里分配空间时,malloc会在分配的空间后加一个结束标记,
所以使用malloc分配的空间时一定不要越界访问,否则会破坏malloc后台维护的数据结构,导致内存空间无法释放等问题。
malloc比较复杂,它会在后台维护一个双向链表的数据结构来管理分配好的空间。
5.分配物理空间
操作系统分配物理内存空间时,会以页为单位,一般一页为4096个字节。
系统对malloc会格外照顾,第一次malloc时,我们的系统会给malloc分配(映射)33个页面的物理内存空间,
以后的malloc就从这33个页面中获取要使用的空间。
6.类Unix操作系统分配内存的函数brk/sbrk
6.1 void* sbrk(int sz);
sz>0 : 将当前位置向后移sz个字节,相当于分配内存空间
sz<0 : 将当前位置向前移sz个字节,相当于释放内存空间
sz==0 : 用来获取当前位置
sbrk的返回值永远是移动之前的位置。
6.2 int brk(void* p);
将当前位置移动到指定位置p处,如果p在当前的位置后面,相当于分配内存空间,否则就相当于释放内存空间。
返回0代表成功,-1代表失败
6.3
sbrk和brk的本质是一样,都是在移动当前位置,所以可以混用。实际中,经常用sbrk分配空间,用brk释放空间。
7. mmap
memory mapping(内存映射)
#include <sys/mman.h>
void* mmap(void* start, size_t length,
int prot, int flags, int fd,
off_t offset);
1)start, 没有映射过的虚拟内存地址
sbrk(0)可以获取一个没有映射过的虚拟内存地址。
可以给start传NULL, 系统会自动找一个没用过的虚拟内存地址进行映射。
一般都传NULL.
2) length : 要映射多大的物理空间
此参数应该以页面的倍数传。如果不是页面的倍数,系统自动按页对齐。
3) int prot : 映射好的内存空间的访问权限
PROT_READ : 读权限 (1)
PROT_WRITE : 写权限 (2)
PROT_EXEC : 执行权限 (4)
可以有几个权限 : PROT_READ | PROT_WRITE
4) int flags : 标记
在映射时不同的选项
MAP_SHARED/MAP_PRIVATE 两个必选其一
MAP_SHARED : 以共享的方式映射
MAP_PRIVATE : 以私有的方式映射
MAP_ANONYMOUS :
匿名映射(专门用来映射内存)
可以几个合并使用:
MAP_PRIVATE | MAP_ANONYMOUS
5) int fd : 文件描述符
仅用于映射到文件,当第四个参数中有MAP_ANONYMOUS时,此参数无效
6) off_t offset : 文件偏移量
仅用于映射到文件
返回值: void*
映射好的内存空间的首地址。
如果映射失败,此函数返回-1
if(mmap(...)==(void*)-1){//失败}
#define MAP_FAILED (void*)-1
if(mmap(...)==MAP_FAILED){//失败}
8.系统调用
操作系统内核给用户程序提供的API
系统调用函数运行在系统态,普通的程序代码运行在用户态。
1.文件
一切都是文件
/dev/tty 终端设备文件(键盘和显示器)
/dev/null 空设备文件(Unix黑洞)
typedef int ssize_t;
typedef unsigned int size_t;
....
off_t
2.打开文件 open
flags:
以下三个必选其一:
O_RDONLY 以只读方式打开
O_WRONLY 只写
O_RDWR 读写
如果是创建文件,标记中加入以下内容:
O_CREAT 创建文件时必须有的
O_EXCL 如果文件已经存在,由失败,返回-1
O_TRUNC 如果文件存在,清空文件
创建文件时必须传第三个参数(文件的访问权限)
O_APPEND : 打开文件后,以追加的方式写文件 uf_end
只要标记之间不冲突,就可以联合使用,如:
O_RDWR | O_CREAT | O_TRUNC
3.read/write
read/write函数都是系统调用函数,运行在系统态,所以,实际开发中,
尽量不要频繁调用这此函数,如果要频繁地输入输出大量数据,应该尽量使用缓冲区,
以减少调用read/write的次数,从而提高效率。
4.文件偏移量
什么时候文件偏移量会移动:
1)lseek函数可以主动移动文件偏移量
2)read会移动文件偏移量
3)write会移动文件偏移量
5.文件加锁
5.1 文件锁的类型:
读锁 : 共享锁, 如果A进程对文件的某个区域加了读锁,
B进程也可以对文件的此区域加读锁,但是不能对此区域加写锁。
写锁 : 独占锁(排他锁), 如果A进程对文件的某个区域加了写锁,
B进程就不能对此区域加写锁, 也不能对此区域加读锁
1. F_GETLK的使用
测试某一个指定的锁是否可以加到文件上。
当调用fcntl(fd, F_GETLK, &lock);之后,如果lock中的值没有发生任何变化(除l_type变成F_UNLCK),
说明锁lock可以加到文件上。
如果测试锁不能加到文件上,那么内核会将导致加不上锁的原因锁保存到测试锁中,
重要的是,原因锁中的l_pid中保存的是另外一个进程(已经加了锁的进程)的进程ID.
2.当文件关闭或进程结束时,文件锁会自动释放。
3.stat
4.文件映射
mmap
struct s{
int a;
int b;
};
struct s e = {1,2}; C89
struct s e = {.a = 1, .b = 2} C99
struct s e = { a : 1, b : 2} GUN C
1.目录操作
1.1 操作目录
mkdir : 创建目录
rmdir : 删除目录(只能删除空目录)
chdir : 改变当前工作目录(进入目录)
getcwd : 返回当前工作目录
1.2 读目录内容
#include <dirent.h>
directory entry : 目录项
打开目录
DIR* opendir(char* name);
读取已打开目录的其中一个目录项
struct dirent* readdir(DIR* dir);
目录项的类型 d_type :
1 : 管道文件
4 : 目录
8 : 普通文件
10 : 软连接文件
12 : Socket文件
2.进程
Solaris进程状态:
S : sleep睡眠状态
O : 可运行状态
T : 挂起状态(阻塞)
R : 运行状态
Z : 僵尸(僵死)进程
进程之间的父子关系:
1) A进程启动了B进程,A就是B的父进程。启动之后父子进程同时运行。如果子进程B先结束,子进程会通知父进程A,父进程会回收子进程的相关资源。
2)父进程启动子进程后同时运行,父进程先结束运行,子进程成为孤儿进程,子进程马上认init为父进程。进程1(init)一般认为孤儿院。
3)父子进程同时运行,子进程先结束,通知父进程,父进程未响应,子进程结束了。父进程的进程表项中依然记录着一个子进程,这个子进程就成了僵尸进程。
3.操作进程
proccess descriptor
getpid, getppid, getuid, geteuid
fork
4.退出进程
4.1 正常退出
在main函数中return,或在进程任何位置调用exit都会导致进程正常结束。进程正常结束前,会自动调用atexit注册在进程上的一些函数,进程的缓冲区也会被清空...
4.2 立即退出
当进程调用了下面两个函数的其中一个时,进程会立即结束,atexit注册的函数不会调用,进程的缓冲区也不会清空...
#include <stdlib>
_Exit(int); //标准C语言版本
或 #include <unistd.h>
_exit(int); //标准Unix版本
1.信号(signal)
信号是一种中断。中断是系统调度一种常见方式。
具体说就是暂停当前任务,来处理更重要的任务。
信号是软件中断。是进程之间的通信技术之一。
信号一般处理的都是异常情况,如:
CTRL+C会产生信号: 直接终止
CTRL+\会产生信号: 退出
内存访问异常,产生信号 : 段错误
文件访问异常,产生信号 : 总路错误
错误的数学运算除零 : 浮点数例外
信号本质上是一个整数,这此整数几乎都有一个名字,这个名字被定义成了宏:
#define SIGINT 2
2.信号的分类
不可靠信号,不支持排队,容易丢失。(1~32)
可靠信号,支持排队,不会丢失。(33~64)
3.信号是如何产生的
3.1 访问资源出现
访问内存出现段错误
文件出现总路错误
除零出现浮点数例外
....
3.2 用户中断
CTRL+C, CTRL+\
3.3 进程调用一些函数发送的信号
kill函数
alarm函数
raise函数
sigqueue函数
setitimer函数
......
3.4 shell发出的信号(命令)
kill命令
4.进程如何处理信号:
4.1 默认,当信号来时,采取默认动作
不同的信号默认动作不一样,绝大部分信号的默认处理动作是退出进程。
4.2 忽略,当信号来时,不做任何处理
4.3 捕获
当信号到来时,捕获信号并调用指定函数处理。
5.注册信号处理函数
typedef void (*sighandler_t)(int);
sighandler signal(int sig,sighandler_t h);
signal函数的本质:
void (*signal(int s,void(*f)(int)))int();
返回值:
如果安装(注册)信号处理器,安装(注册)成功,signal函数返回处理器(信号处理函数)本身。如果失败,返回-1。 (void(*)(int))-1;
#define SIG_ERR (void(*)(int))-1
if(signal(3, fa)==SIG_ERR){
perror("安装失败"),exit(-1);
}
6.子进程的信号问题
6.1 子进程会继承父进程的信号处理方式。
6.2 但是,如果子进程是调用了exec函数的,那么父进程中忽略的信号处理方式会被子进程继承,捕获并调用函数处理的信号会被子进程恢复成默认行为。
7.发送信号的函数
7.1 kill函数
int kill(pid_t pid, int signo);
pid:
>0 : 给pid进程发送信号signo
==0 : 给当前进程组的所有进程发signo
==-1 : 给有权限发送的所有进程发signo
<-1 : 给指定进程组(pid的绝对值)发signo
signo :
要发送的信号
0 : 并不真正的发送信号,只是用来测试能否则给进程发送,如果可以发,函数返回0
返回值: 0 : 发送成功
-1 : 发送失败
7.2 raise函数
只是给自己(当前进程)发信号,相当于:
kill(getpid(), sig);
7.3 alarm函数
类似计数器
7.4 setitimer 计数器
7.5 sigqueue
8. sleep
是一个不可重入函数,如malloc也是不可重入函数。
pause
阻塞当前进程,等信号到来。信号到来后,进程先处理信号(默认,忽略,调用函数),处理完后pause函数会返回-1
9.信号屏蔽
9.1 信号集
信号集是一个变量,此变量可以表示多个信号。信号集变量的类型是sigset_t
9.2 设置进程信号屏蔽字
int sigprocmask(int how, sigset_t*new,sigset_t* old);
new : 要设置的新的信号屏蔽字
old : 将以前的信号屏蔽字保存到此变量中
how:
SIG_BLOCK 将新的信号屏蔽字填加到当前信号屏蔽字中。
1 2 3 + 3 4 5 = 1 2 3 4 5
SIG_UNBLOCK 从当前屏蔽字中删除指定的信号集
1 2 3 + 3 4 5 = 1 2
SIG_SETMASK 设置当前信号屏蔽为new
1 2 3 + 3 4 5 = 3 4 5
10. sigaction函数
10.1 函数功能:
安装(注册)一个信号处理器,和 signal是一样的,但功能比signal多一点。
10.2 struct sigaction
struct sigaction{
sa_handler : 一般情况使用信号处理函数
sa_sigaction : 需要信号的详细信息时使用的信号处理函数
sa_mask : 正在调用信号处理函数期间自动屏蔽掉的信号集
flags : 标记
};
flags:
SA_RESETHAND/SA_ONESHOT :
当执行完第一次信号处理函数后,将此信号的处理方式恢复成默认行为
SA_NODEFER/SA_NOMASK :
当正在处理一个信号时,如果此信号现次到,马上处理新的信号。
SA_SIGINFO :
信号处理函数将使用sa_sigaction,并且信号处理函数可以获取此信号相关的一些信息,如:发送者,信号携带的数据等....
1.进程组
将多个进程编组,可以形成进程组。多个进程放入一个组可以统一对这些进程进行操作。
setpgrp(); //设置进程组
setpgid(pid_t pid, pid_t gpid); //将进程放入进程组
kill(-pid, 2);//组指定的进程组发信号
2. 发送信号函数sigqueue
此函数在发送信号的同时,可以携带一个数据。
联合 union
union{
int x;
char cs[4];
};
x=0x41424344;
cout << cs << endl;//DCBA
3.计时器
每一个进程都有三个计时器(由内核维护):
真实计时器(Real iTimer)
程序运行总时间(用户态+系统态+睡眠)
虚拟计时器(Virtual iTimer)
用户态时间
实用计时器(Prof iTimer)
用户态+内核(系统)态
4.进程间通信
Inter Process communication(IPC)
管道
XSI IPC :
共享存储段
消息队列
信号量集
Socket通信
5.管道通信
5.1 创建管道文件
5.1.1 用命令mkfifo创建
5.1.2 在程序中用函数mkfifo创建
5.2 用读写文件方式进行通信
1)打开管道文件
2)以读写文件的方式通信
3)关闭文件
4)删除管道文件
5.3 无名管道
不必在磁盘上创建管道文件,依赖的管道文件在内存中自动创建:
pipe 函数会自动创建管道文件并打开。
无名管道只能用于父子进程之间的通信。
6. 共享存储段
6.1 生成key
6.2 创建共享存储段
6.3 获取已有的共享存储段的ID
6.4 挂接(映射)到共享存储段
6.5 脱接(解除映射)共享存储段
6.5 shmctl函数
IPC_STAT
IPC_SET
IPC_RMID
进程间通信
管道
XSI IPC
1. XSI IPC通信的共性:
共享存储段,消息队列,信号量集
1.1 必需有key,key是创建和获取IPC通信结构的唯一依据。
1.2 生成key:
1)直接使用IPC_PRIVATE做为key.这种办法很少很用,因为IPC_PRIVATE只能在创建IPC结构时使用。
2)在共享头文件中直接定义key的值。
3)调用ftok函数生成key
ftok函数要求路径是真实的。
当前目录是:/home/soft01/unixc/day09
key1 = ftok(".", 100);
key2 = ftok("/home/soft01/unixc/day09",100);
key1 == key2 ?
1.3 都需要IPC结构通信。
IPC结构是内核创建并管理的一个通信媒介。
1.4 创建和获得ID都是同一个函数,创建时都需要指定标志:IPC_CREAT,如果想创建的IPC结构已经存在,那么IPC_EXCL就会起作用。另外,标志中一定要有权限:
IPC_CREAT|IPC_EXCL|0600
1.5都有一个控制函数
shmctl, msgctl, semctl
这此函数有共同的功能:
IPC_STAT : 用来获取IPC结构的属性,并保存到指定的结构中。
IPC_SET : 用来修改权限
IPC_RMID : 用来删除IPC结构
2.消息队列
2.1消息的分类
无类型消息:
任何类型的数据都可以做为消息发送和接收
有类型消息:
有类型消息一定是一个结构体,而且结构的第一个成员一定是long类型的成员,表示消息的类型:
struct msg{
lont mtype;//类型
//..消息的数据
};
2.2 无类型的消息
发送无类型消息时,直接发送就可以了。
1.信号量集(Semaphore Array)
集中可以有多个计数器。可以对多个资源进行保护,保护指限制访问这此资源的进程数量。
1.1 使用信号量集:
1)生成key
2)创建信号量集
3)初始化信号量集
4)使用信号量保护资源
2. 网络通信
Socket通信
2.1 Socket
插座,套接字
在Unix网络通信中,socket可以认为是一个文件,这个文件可以socket文件,也可以是网络。专门有socket描述符来表示一个socket.
2.2 IP
在网络中唯一标识一台计算机
192.168.182.53
255.255.255.0
-----------------------
192.168.182.0 网络地址
53 主机地址
2.3通信模型
1)创建一个socket
2)准备通信地址
3)绑定
4)通信
5)关闭socket
2.4 socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain : 域, 用来选择通信使用的协议簇(protocol family)。
PF_UNIX,PF_LOCAL,PF_FILE
AF_UNIX,AF_LOCAL,AF_FILE : 本地通信
PF_INET,AF_INET : 网络通信(TCP/IP) IPv4
PF_INET6,AF_INET6:网络通信(TCP/IP) IPv6
PF_IPX : Novell通信协议
PF_PACKET : 内核通信协议
type : 通信类型
SOCK_STREAM : 以数据流的方式通信
SOCK_DGRAM : 以数据报的方式通信
protocol : 通信协议
一般情况下是无效的。经常传0
前两个参数基本上可以确定通信协议。
返回值:
socket描述符(类似于文件描述符)
返回-1,说明创建socket失败
2.5准备通信地址
#include <sys/socket.h>
struct sockaddr{
sa_family;//协议簇
sa_data;//地址
};
本地通信地址:
#include <sys/un.h>
struct sockaddr_un{
int sun_family;//协议簇
char sun_path[];//socket文件路径
};
TCP/IP通信地址:
struct sockaddr_in{
sin_family;//协议簇00010010
short sin_port;//端口号
sin_addr;//IP地址
};
2.6 端口号
用来定位一个进程的整数值。IP可以定位一台网络上的计算机,端口号可以定位这个计算机上的一个进程。
是一个unsigned short类型的整数,范围0~65535,逻辑上一个端口号代表一个进程。
默认端口号:
http: 80
www.tarena.com.cn ==> http://www.tarena.com.cn:80 ==> http://212.34.12.89:80 ==> 服务进程(web服务) ==>html==>回到浏览==>解析并显示html页面。
ftp : 21
telnet : 23
0~1024之间的端口号一般都被系统使用。
网络字节序和本地字节序的转换:
htons
htonl
ntohs
ntohl
2.7 IP地址
2.7.1 IP地址的表示方式:
1)点分十进制
192.168.182.53
2)十六进制(二进制)
C0A8B635
3)整数 3232282165
2.7.2 IP地址转换:
inet_addr : 点分十进制=>整数
3.TCP通信
一对一 一对多(C/S) Client/Server
面向连接,可靠的 传输协议
3.1 Server:
1)创建socket
2)准备地址
3)绑定
4)监听
listen(int sockfd, int len);
让服务最多准备多长的队列缓存申请连接的客户端。
5)等待客户连接
int accept(int sockfd, struct sockaddr*addr, sockaddr_t* len);
addr: 用来客户端的地址
len: 必需有值(输入输出参数)
返回值:成功后,会返回一个socket,此socket已经和客户端建立了连接关系,服务要和客户端通信,就使用此socket
6)和客户端通信
7)关闭
3.2 客户端
4.UDP通信
无连接的,不可靠的 传输协议
1.UDP通信
1.1 server
1)创建socket
2)准备地址
3)绑定
4)通信
5)关闭
1.2 client
1)创建socket
2)准备服务器端地址
3)连接(可以连接,也可以不连接)
4)通信
5)关闭
2.多线程
2.1线程
执行中的一段程序序列。
和进程的区别:
1)线程是进程一部分,属于进程
2)进程是资源分配的基本单位,拥有自己独立的内存空间。
3)线程没有自己独立的内存空间,进程中的多个线程共享共享的所有资源(内存...)
4)启动一个进程需要消耗更多的系统资源和系统时间,而启动一个线程需要的资源和时间较少。
进程是重量级的,线程是轻量级的。
2.2 启动线程
#include <pthread.h> ==> libpthread.so
POSIX规范中规定的多线程整个方案。
int pthread_create(
pthread_t* id,//线程ID
pthread_attr_t* attr,//线程属性
void*(*f)(void*),//线程任务
void* p);//任务参数
id : 输出参数,返回线程id
attr : 用来设置线程的属性,NULL表示默认
f : 函数表示线程的任务,线程启动后会自动调用此函数
p : 给f函数传的参数
返回值:
成功返回0,失败返回错误原因(错误代码)
多线程函数大部分都如此设计返回值。
2.3 主线程和普通线程
当线程成功创建后,直接开始运行。main函数是主线程,一个进程中只要主线程结束,进程就会退出,进程退出时,进程中的线程直接消失。
进程中,多个线程在一个时间段内是并发(同时)的运行, 但在一个时间点,只有一个线程运行(CPU只有一个)。
2.4 多线程作用:
提高程序运行效率。由多个线程同时执行多个任务。
2.5 给线程函数传参数
2.6 线程函数的返回值
只能返回全局变量的地址或静态变量的地址等,总之,返回的地址指向的空间不能随线程的结束而释放。
3.线程函数介绍
3.1 返回当前线程的ID:
pthread_self
3.2 判断两个线程是否是同一个
pthread_equal(pthread_t id1,pthread_t id2);
3.3 线程终止
1)线程函数结束,线程终止。
2)线程函数中调用pthread_exit,线程终止
3)被别的线程终止,或自身出问题
3.4 线程分离
处理分离状态的线程不能被join
pthread_detach
3.5 线程取消
一个线程可以取消另外一个线程的运行,前提是被取消的线程允许取消。一个线程默认是允许被取消的。
pthread_cancel :取消指定线程
可以让线程不被取消:
pthread_setcancelstate(int , int*);
PTHREAD_CANCEL_ENABLE : 打开取消
PTHREAD_CANCEL_DISABLE : 关闭取消
设置取消类型:
pthread_setcanceltype(int, int*);
PTHREAD_CANCEL_DEFERRED : 取消点取消
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消
4.线程属性
1)定义属性变量
pthread_attr_t attr;
2)初始化变量
pthread_attr_init
3)设置具体的属性
pthread_attr_setxxxxxxx
4)启动线程
pthread_create(&id, &attr, ....);
5)销毁属性变量
pthread_destroy(&attr)
5.线程同步
一个进程中的多个线程是共享进程中的相关资源的,那么,在多个线程同时对同一个数据进行访问(读写)时,
会造成数据的不完整和不一致。为了避免这种问题,我们就需要对多个线程进行同步协调。
5.1 互斥量(互斥锁)
5.2 信号量
5.3 条件变量
附:
restrict:
C99新增语法,此关键字只能出现在函数的指针参数上。主要作用是提高此指针参数在函数中的访问效率。
struct Data{
int x;
int y;
char name[];
//...
};
void fa(struct Data*restrict d){
for(int i=0; i<1000000; i++){
d->x
d->y
d->name
}
}
void fa(struct Data d){
for(int i=0; i<1000000; i++){
d.x
d.y
d.name
}
}
线程:
1)相关函数调用
2)理解 并发运行
并发量
1.线程并发访问临界资源
2.互斥锁(互斥量) mutex
是一个变量,代表一把锁。独占锁,排它锁
1)定义一个互斥量(变量)
类型: pthred_mutex_t
pthread_mutex_t lock;
2)初始化互斥量
pthread_mutex_init
或 :
lock = PTHREAD_MUTEX_INITIALIZER;
3)加锁
pthread_mutex_lock(&lock);
4)解锁
pthread_mutex_unlock(&lock);
5)销毁
pthread_mutex_destroy(&lock);
3.死锁
两个线程A,B,两个互斥量 lock1,lock2
A {
lock(&lock1);
...//访问共享资源1
lock(&lock2);
...//访问共享资源2
unlock(&lock2);
unlock(&lock1);
}
B {
lock(&lock2);
...//访问共享资源2
lock(&lock1);
...//访问共享资源1
unlock(&lock1);
unlock(&lock2);
}
如果线程A和线程B 同时运行,有可能发生死锁。
避免死锁的经验:
顺序上锁,反向解锁,不要回调。
是变量,当计数器用
#include <semaphore.h>
1)定义信号量(变量)
sem_t sem;
2)初始化信号量值(给计数器赋初始值)
sem_init(sem_t* sem, int ps,int value);
sem: 要初始化的信号量
ps :
0 : 线程间共享的信号量
非0 :进程间共享的信号量
value :信号量的初始值
3)获取一个信号量(减1)
sem_wait(&sem)
当信号量的初始值为1时相当于加锁
4)释放一个信号量(加1)
sem_post(&sem)
当信号量值为0时相当于解锁
5)销毁
sem_destroy(&sem)
两种用法:
1)初始化为1,当锁来用,相当于互斥量
2)初始化为一个整数,用于限制同时访问某资源的线程数,相当计数器。
5.条件变量
5.1 生产者和消费者问题
数据 IT
5.2 使用条件变量,可以让当前线程阻塞
当某个条件成立时,当前线程已经无法继续运行,让当前线程主动进入阻塞状态。
1)是一个变量,类型pthread_cond_t
pthread_cond_t full;
2)初始化条件变量
pthread_cond_init函数
或 full = PTHREAD_COND_INITIALIZER;
3)当条件成立,让当前线程阻塞:
pthread_cond_wait(
pthread_cond_t*cond,
pthread_mutex_t*mutex);
cond: 让当前线程阻塞在此变量中。
mutex: 线程阻塞前释放该锁
4)其他线程唤醒被阻塞在条件变量中的线程
唤醒一个:
pthread_cond_signal(pthread_cond_t*);
唤醒所有:
pthread_cond_broadcast(pthread_cond_t*);
5)销毁
pthread_cond_destroy