linux系统编程
基础命令
- 根目录下的一些目录
bin目录
- 存放可执行文件
dev目录
- 存放设备文件 : 字符设备,块设备
boot目录
- 存放开机启动相关
etc目录
- 存放用户当前的配置信息文件 passwd group
lib目录
- 库文件 libc.so.6
usr目录
-
用户资源管理目录 (第三方库)
-
cd ~ 返回家目录
-
cd - 返回上一次目录
-
ls -d 查看目录信息
linux系统文件类型
- 普通文件: -
- 目录文件 : d
- 字符设备文件: c
- 块设备文件: b
- 软连接: l
- 管道文件: p
- 套接字: s
cp 拷贝文件命令
-
拷贝目录
-
cp dir1 dir2 -r cp dir1 ~/ -r
软硬链接
-
软连接 类似windows下的快捷方式
-
软连接创建,为保证软连接任意搬移,务必保证用绝对路径
-
硬链接特征,对任意一个修改,都变化
-
硬链接通过inode(编号)连接在一起
-
操作系统给每一个文件赋予唯一的 inode,当有相同inode的文件存在时,彼此同步。删除时,只将硬链接计数减一。减为0时, inode被释放。
创建用户
- sudo adduser 用户名
设置密码
- sudo passwd 用户名
创建用户组
- sudo addgroup 新组名 //修改所属组 sudo chgrp 新用户组名 待修改文件
修改文件所属用户组和用户组名
-
sudo chown nobody:nogroup a.c
删除用户
- sudo deluser 用户名
find 命令
- -type 按文件类型搜索
- -name 按文件名搜索
- -maxdepth 指定搜索深度
注意:find命令先写文件名在写指令
find /home/itcast -size +20M -size -50M //单位 k M G
grep命令
- 找文件内容
grep 文件内容
ps aux | grep 内容
grep -r 'copy' ./ -n -n//显示行号
安装软件包
-
更新软件列表到本地 sudo apt-get update
-
卸载 sudo apt-get remove
-
deb包安装 (类似window中的exe文件)
-
sudo dpkg -i xxx.deb //安装deb软件包命令
-
aptitude类似于apt-get 不过多了show命令
tar压缩
-
tar -zcvf
要生成的压缩包名 -
gzip file 只能压缩一个一个文件
-
tar
命令就是进行打包的 上面z
指gzip
c
表示创建v
指显示压缩过程f
表示生成文件 -
bzip2
也是用于压缩文件,类似于``gzip` -
tar jcvf test.tar.gz file1 dir2
-
解压缩
-
tar zxvf test.tar.gz
//用gzip进行解压缩
rar压缩
rar a -r newdir dir//压缩
unrar x newdir.rar //解压缩
zip压缩
zip -r dir.zip dir //压缩
unzip dir.zip //解压缩
其他命令
who
- 查看当前在线上的用户情况
ps
- 用于监控后台进程的工作情况
jobs
-
显示当前shell下正在运行哪些作业
-
fg``bg
前后台切换
kill
- 杀死进程
env
- 显示当前进程的环境变量
ifconfig
- 查看网卡信息 //ip地址
alias
- 起别名
alias ls='ls --color=auto'
umask
- 指定用户创建文件时的掩码
file命令
- file命令用于确定文件类型。它会读取文件的内容或者元数据,然后根据这些信息判断文件的类型。file命令通常用于识别未知文件的类型,以便正确地处理它们。
gcc 参数
-
gcc -I
指定头文件所在目录位置 -
gcc -c
只做预处理,编译,汇编操作 -
gcc -g
编译时添加调试语句 -
-On
优化级 -
-Wall
提示更多警告信息 -
-D
向当前程序中注册一个宏
静态库和共享库
静态库制作
ar rcs libmylib.a file12.o
gcc test.c libmymath.a -o test //使用静态库
- 没有函数定义和声明时 ,编译器会进行隐式声明
- 不过格式为int 函数(int, int)
动态库的制作和使用
- 注意:普通函数:链接之前main函数地址为0;
- 链接之后会地址回填 main函数地址改变
- 动态库生成与位置无关的代码 //延迟绑定
//动态库的制作
将.c文件生成.o文件(生成与位置无关的代码 -fPIC)
gcc -c add.c -o add.o -fPIC
使用gcc -shared 制作动态库
gcc -shared lib库名.so add.o sub.o div.o
编译可执行程序时,指定所使用的动态库。 -l: 指定库名(去掉lib前缀和.so后缀 -L:指定路径。 )
gcc test.c -o a.out -l mymath -L ./lib
注意:需要提供工作动态库所在目录位置。
通过环境变量: export LD_LIBRARY_PATH= 动态库路径
./aout 成功
ldd 命令
用于列出程序运行所需的共享库依赖关系,当你对一个可执行文件运行ldd命令时,它显示该文件所依赖的共享库及其路径。
页面大小
- linux常见页面大小为4KB
命名空间
-
就是把单纯的把一堆名字放到一个盒子里
-
using namespace std 就相当于把这个盒子里所有名字倒出来
-
:: 查找全局这个盒子里的名字,std:: 查找 std 命名空间这个盒子里名字
gdb调试
- -g :使用该参数编译可执行文件,得到调试表
- list: 列出源码
- d : d 20 在第20行位置断点
- p : 查看变量的值
- continue: 继续执行断点后续指令
- quit : 退出gdb当前调试
- run : 使用run查找段错误出现位置
- finish : 结束当前函数调用
- set args :设置main函数命令行参数
- info b: 查看断点信息表
- ptype : 查看变量类型
栈帧
- 随着函数调用而在stack上开辟的一片内存空间。用于存放函数调用时产生的局部变量和临时值。
makefile
-
两个函数:
-
src = $(wildcard ./*.c):匹配当前工作目录下的所有.c文件 将文件名组合成列表,赋值给变量 src obj = $(patsubst %.c, %.o, $(src)):将参数3中,包含参数1的部分,替换为参数2.
-
clean
-
- -rm rf $(obj) a.out ( -表示失败,还会继续操作)
模式规则
- %.o : %.c
gcc -c $< -o %@
静态模式规则
- $ (obj) : %.o :% .c
- gcc -c $< -o %@
伪目标
- .PHONY: clean ALL
系统调用
- 系统调用是操作系统提供给外部程序的编程接口:
- 用户调用printf函数 调用 write系统函数 调用sys_write 系统调用
open函数
int open (char* pathname ,int flags,mode_t mode)
参数:
pathname 打开文件路径名
flags 文件打开方式 O_RDONLY|O_WRONLY|O_RDWR O_CREAT...
mode 使用前提 flags是O_CREAT 取值为八进制,表示访问权限 rwx
创建文件最终权限 = mode & ~umask
返回值:
成功,返回所对应的文件描述符
失败 返回-1 设置errno (系统保存的变量)
int close (int fid);
错误处理:
strerror(errno)会返回对应errno的错误信息,字符串类型
read函数
-
ssize_t read ( int fd ,void* buf ,size_t count); 参数: fd:文件描述符 buf: 存储数据的缓冲区 count: 缓冲区大小 返回值: 0:读到文件末尾 成功 : 读到的字节数 失败: -1,设置 errno -1 并且 errno = EAGIN或EWOULDBLOCK 说明不是read失败,而是read以一种非阻塞的方式读一个设备文件(网络文件),并且文件无数据
write函数
-
ssize_t(int fd,const void* buf,size_t count) 返回值: 成功:写入的字节数 失败: -1 ,设置errno
文件描述符:
PCB控制块:本质 结构体
成员 : 文件描述符表。
文件描述符: 0/1/2/3… /1023 表中可用的最小
0-STDIN_FILEEND
1-STDOUT_FILEEND
2-STDERR_FILEEND
阻塞和非阻塞
- 是设备文件和网络文件的属性
产生阻塞的场景。读设备文件,网络文件。(读常规文件无阻塞概念)
/dev/tty —终端文件
open(“/dev/tty ”,O_RDWR|O_NONBLOCK) —-设置 终端文件为非阻塞状态(默认为阻塞状态)
fcntl函数
-
改变文件性质
-
int fcntl (int fd, int cmd ….)
int flags = fcntl (fd, F_GETFL)
flags |= O_NONBLOCK
fcntl(fd, F_SETFL, flags)
改变文件性质为非阻塞文件
lseek函数
-
改变文件的读写位置
-
off_t lseek( int fd, off_t offset, int whence); whence参考位置: SEEK_SET 文件开头 SEEK_CUR 当前位置 SEEL_END 末尾 成功返回新的读写位置(相对于文件开头的偏移量) 失败时返回-1,并设置errno
-
使用lseek拓展文件大小,要想真正拓展,必须引起IO操作
truncate函数
-
截断文件长度或指定长度,常用来拓展文件大小
-
int truncate (const char* path, off_t length)
可以直接拓展文件, int ret =trucate (‘’…‘’,250);
传入参数:
1.指针作为函数参数。
2.同常有const关键字修饰。
3.指针指向有效区域,在函数内部做读操作。
传出参数;
1.指针作为函数参数。
2.在函数调用之前,指针指向的空间可以无意义,但必须有效。
3.在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数;
1.指针作为函数参数。l
2.在函数调用之前,指针指向的空间有实际意义。
3.在函数内部,先做读操作,后做写操作。
4函数调用结束后,充当函数返回值。
stat 函数
- 获取文件属性(从inode机构体中获取)
int stat (const char* path ,struct stat *buf)成功返回0;失败返回-1,设置errno
参数一:文件名
2:inode结构体指针(传出参数)
- istat函数不同之处 istat函数不会穿透符号链接
- 文件类型判断发方法:
- st_mode 取高四位 使用对应的宏
access 函数
-
测定指定文件是否存在或拥有某种权限
-
int access (const char* pathname, int mode) 参数2: F_OK 判断文件是否存在
chmod函数
-
修改文件的访问权限
-
int chmod (const char * path,mode_t mode)
进程
getcwd函数
-
获取进程当前工作目录
-
char * getcwd(char*buf,size_t size)buf 中保存当前进程工作目录
-
失败返回 NULL;
chdir函数
- 改变当前进程工作目录
目录操作函数
DIR * opendir(char *name) ;
int c1osedir(DIR *dp);
struct dirent *readdir(DIR * dp ) ;
struct dirent {
inode
char dname [256];
}
- linux中
ls -R
表示递归列出当前目录下的文件和子目录 sprintf
函数 功能是将格式化的数据输出到字符串走
dup和dup2函数
int dup (int oldfd) 文件描述符复制
返回:新的文件描述符
int dup2 (int oldfd,int newfd) 重定向
进程控制块PCB
- 每个进程在内核都有一个进程控制块(PCB),linux内核的进程控制块是
task_struct
结构体 - 该结构体内部成员
- 进程id。系统中每个进程有唯一的id,在c语言中用
pid_t
类型表示,其实就是一个非负整数。 - 进程的状态,就绪,运行,挂起,停止等
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)。
- umask掩码。
- 文件描述符表,包含很多指向file结构体的指针。
- 和信号相关的信息。
- 用户 id和组id
- 会话(Session)和进程组。
- 进程id。系统中每个进程有唯一的id,在c语言中用
fork函数
# include<unistd.h>
pid_t fork(void);
-
创建子进程
-
父子进程各自返回,父进程返回子进程pid,子进程返回0
getpid() getppid()函数
-
#include<unistd.h> #include<sys/types.h> pid_t getpid(void); pid_t getppid (void);
进程共享
- fork()后
- 父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式…
- 父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集·
似乎,子进程复制了父进程0-3G 用户空间内容,以及父进程的 PCB,但 pid 不同。真的每.fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?,
- 父子进程遵循写时共享读时复制——一般是 全局变量
- 还共享 文件描述符 mmap建立的映射区
gdb调试
-
gdb调试只会跟踪一个进程,可以在fork之前设置跟踪父进程还是子进程
-
设置父进程调试路径: set fol1ow-fork-mode parent (默认)
-
设置子进程调试路径: set fol1ow-fork-mode child
exec函数族
-
使进程执行某一程序。成功无返回值,失败返回-1
-
int execlp(const char* file,const char* arg,...) 借助PATH环境变量寻找执行程序 参1 程序名 参2 argv 0 参3 agrv 1 哨兵 NULL
-
int execl(const char*path ,const char* arg,...) 自己指定待执行程序路径
孤儿进程
- 父进程先于子进终止,子进程沦为“孤儿进程”,会被init进程领养。
僵尸进程
- 子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。
ps指令
wait函数
-
回收子进程退出的资源
-
pid_t wait (int *status)
-
参数(传出) 回收进程的状态
-
返回值: 成功 返回回收进程pid 失败 -1 errno
-
函数作用1:
- 阻塞等待子进程退出
-
函数作用2
- 清理子进程残留在内核的 pcb资源
-
函数作用3
- 通过传出参数,得到子进程结束状态
-
获取子进程正常终止值:
- 宏函数WIFEXITED(status) 为真 调用WEXITSTATUS(status) 得到 子进程的退出值
-
获取导致子进程异常终止的信号:
- IFSIGNALED(status) 为真 调用VTERMSIG(status) 得到导致子进程异常终止的信号编号。
waitpid函数
-
指定某一进程进行回收
-
pid_t waitpid(pid_t pid, int *status ,int options)
-
参数:
-
pid : 指定回收的子进程pid
-
大于 0 待回收的子进程pid
-
-1。任意子进程
-
0 。同组的子进程。
-
status: 传出 回收进程的状态。
-
options : VNOHANG指定回收方式为 , 非阻塞。
-
返回值:
-
大于0∶表成功回收的子进程pid
-
0:函数调用时,参3指定了WNOHANG,并且,没有子进程结束。
-
-1:失败。errno
-
-
注意:wait和waitpid函数:一次调用,回收一个子进程
管道
概念
-
进程间通信
- ①管道(使用最简单)
- ②信号(开销最小)
- ③共享映射区(无血缘关系)
- ④本地套接字(最稳定)
-
管道是一种最基本的
IPC
机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe
系统函数即可创建一个管道。 -
实现原理:内核借助环形队列机制,使用内核缓冲区实现。
-
特质:
- 1.伪文件(实为内核缓冲区)
- 2.管道中的数据只能一次读取。
- 3.数据在管道中,只能单向流动。
-
局限性:
- 1.不能自己写,自己读。
- 2.数据不可以反复读。一旦读走就不存在了
- 3.半双工通信。
- 4.血缘关系进程间可用。
pipe函数
-
int pipe(int pipefd[2]); 成功: 0 失败 :-1 errno
-
函数调用成功返回
r/w
两个文件描述符 不用open 需要手动close 规定fd[0]->r
读端;fd[1]->w
写端. -
父子进程,兄虎进程间通信
fifo管道
- 无血缘关系进程间通信
- 命名管道: mkfifo
- 无血缘关系进程间通信:
- 读端: open fifo O_RDNLY
- 写端 open fifo O_WRONLY
读管道
1,管道有数据 read返回实际读到的字节数
2,管道中无数据 管道写端被全部关闭 返回0;
管道写端没有全部关闭 read阻塞等待
写管道
1,管道读端全部关闭,进程异常终止
2,管道读端没有全部关闭
管道未满 返回实际写入的字节数
管道已满 write函数阻塞
文件实现进程间通信
- 打开的文件是内核的一块缓冲区,多个无血缘关系的进程,可以同时访问该文件。
共享内存映射
存储映射IO
使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read
和 write
函数的情况下,使用地址(指针)完成IO操作。
这个映射工作可以通过mmap
函数完成
mmap函数
void * mmap (void* addr ,size_t length,int prot,int flags,int fd , off_t offset); //创建共享内存映射
参数:
addr :指定映射区的首地址。通常传NULL,表示让系统自动分配
length: 共享内存映射区的大小。(<=文件的实际大小)
prot : 共享内存映射区的读写属性。 PROT_READ、 PROT_WRITE、PROT_READ|PROT_WRITE
falgs : 标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE
fd : 用于创建共享内存映射区的那个文件的文件描述符。
offset: 默认0,表示映射文件全部。偏移位置。需是4的整数倍。
返回值:
成功: 映射区的首地址
失败: MAP_FAILED 。errno
mmap 父子进程通信
-
要在创建映射区的时候指定对应的标志位参数flags;
- MAP_PRIVATE(私有映射) 父子进程各自独占映射区
- MAP_SHARED(共享映射) 父子进程共享映射区
-
父进程先创建映射区: open(O_RDWR) mmap(MAP_SHARED)
-
fork()创建子进程
-
注意:mmap数据可以重复读取
-
fifo 数据只能一次读取
匿名映射区
p = (int*)mmap (NULL,40,PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS ,-1,0);
其中:fd =-1;
信号
-
信号共性:简单,不能携带大量信息,满足条件才能发送
-
信号是软件层面上的“中断”,程序立即终止处理信号,所有信号的产生及处理全部都是有内核完成。
-
信号相关的概念:
-
产生信号:
-
1,按键产生
-
2.系统调用产生
-
3.软件条件产生
-
4.硬件异常产生
-
5.命令产生
概念。
-
未决。产生与递达之间状态。
-
递达。产生并且送达到进程。直接被内核处理掉。
-
信号处理方式 : 执行默认处理动作、忽略、捕捉(自定义)
-
阻塞信号集(信号屏蔽字)﹔本质。位图。用来记录信号的屏蔽状态。一旦被屏蔽的信号,在解除屏蔽前,一直处于未决态。未决信号集:本质。位图。用来记录信号的处理状态。该信号集中的信号,表示,已经产生,但尚未被处理。
-
默认动作
- Term 终止进程
- ign 忽略信号(默认不处理该信号)
- core 终止进程,生成core文件, (查验进程死亡原因,用于 gdb 调试)
- Stop 停止进程
- Cont 继续运行进程
信号四要素
- 信号编号,信号名称,信号对应事件,信号默认处理动作
常用信号
- 1 SIGHUP 当用户退出 shell时,由该shell启动的所有进程将收到这个信号默认动作为终止进程
- 2 SIGINT 当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
- 3 SIGQUIT 当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出信号。默认动作为终止进程。
- 注:
Ctrl+\
和Ctrl+C
都是用于终止正在运行的程序,但Ctrl+\
发送的是退出信号,而Ctrl+C
发送的是中断信号。Ctrl+C
更常用于正常中断程序的运行,而Ctrl+\
更为强制,可能会导致程序生成core文件。
- 注:
- 9 SIGKILL 无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
- 14 SIGALRM 定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
- 15 SIGTERM 程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
- 17 SIGCHLD 子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
- 19 SIGSTOP 停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
kill函数
-
kill函数给指定的进程发送信号,不一定杀死
-
int kill (pid_t pid, int sig); 成功:返回0, 失败: -1,errno 参数: sig:不推荐使用数字, 用宏名 pid>0 发送信号给指定进程 pid=0 发送信号给调用kill函数的那个进程组的全部进程 pid<-1 发送信号给|pid|进程组的全部进程 pid =-1 发送信号给有权限发送的所有进程
-
alarm函数
-
设置定时器(闹钟)。在指定
seconds
后,内核会给当前进程发送 14 SIGALRM 信号。进程收到该信号,默认动作终止。 -
每个进程都有且只有唯一空间
-
unsigned int alarm(unsigned int seconds);
返回o或剩余的秒数,无失败。 -
常用:取消定时器 alarm(0) ,返回旧闹钟余下秒数
setitimer函数
int setitimer(int which , const struct itimerva1 *new_value , struct itimerva1* o1d_va1ue);
参数:
which:
ITIMER_REAL:采用自然计时。——>SIGALRMI
ITIMER_VIRTUAL:采用用户空间计时―---> SIGVTALRM
ITIMER_PROF:采用内核+用户空间计时--->SIGPROF
old_value :传出参数,上次定时剩余时间。
信号集操作函数
-
信号集操作函数
-
sigset_t set ;自定义信号集。 本质是位图 sigemptyset(sigset_t *set); 清空信号集 sigfi11set(sigset_t *set); 全部置1 sigaddset(sigset_t *set,int signum); 将一个信号添加到集合中 sigdelset(sigset_t *set,int signum); 将一个信号从集合中移除 sigismember (const sigset_t *set,int signum);判断一个信号是否在集合中。在--》1,不在--》0
-
设置信号屏蔽字和解除屏蔽
-
注意:屏蔽信号,只是将信号处理延后执行(延迟至解除屏蔽),忽略表示将信号丢处理
-
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset) how: SIG_BLOCK: 设置阻塞 SIG_UNBLOCK : 取消阻塞 sIG_SETMASK : 用自定义set替换mask 。 set: 自定义的set oldset : 旧有的mask
-
查看未决信号集:
int sigpending(sigset_t*set)
- set 传出的未决信号集
信号捕捉
signal函数
- 注册一个信号捕捉函数
typedef void (*sighandler t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sigaction函数
更常用
- 修改信号处理动作
int sigaction (int signum, const struct sigaction *act ,struct sigaction *oldact )
成功:0
失败:-1 errno
参数:
act 传入参数,新的处理方式
oldact 传出参数,旧的处理方式
struct sigaction 结构体
struct sigaction{
void (*sa_handle)(int);
void(*sa_sigaction)(int, siginfo t*,void*)
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
捕捉特性:
- 1.捕捉函数执行期间,信号屏蔽字由mask --> sa_mask ,捕捉函数执行结束。恢复回mask.1.捕捉函数执行期间,信号屏蔽字由口罩->SA_MASE,捕捉函数执行结束。恢复回面具。
- 2.捕捉函数执行期间,本信号自动被屏蔽(sa_f1gs = 0).
- 3.捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只执行一次
进程组和会话
- 进程组是一组相关进程的集合,共享一个
PGID
这个进程组的首进 进程PID和PGID
相同 他负责管理和协调进程组中其他进程 - 会话:当用户登录到系统后,会启动一或多个进程,这些进程以及所属进程组同属于一个会话。,每个会话有个会话id
SID
- 会话提供了用户与系统交互的框架。
守护进程
daemon
进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。不受用户登录注销影响。通常采用以a结尾的命名方式。
守护进程创建步骤:
fork
子进程,让父进程终止- 子进程调用
setsid()
创建新会话 - 通常根据需要,改变工作目录位置
chdir()
- 根据需要,设置
umask
文件权限掩码 - 根据需要,关闭/重定向 文件描述符
- 守护进程,业务逻辑
线程
-
轻量级进程(LWP),创建底层函数
clone
-
线程可以看做寄存器和栈的集合
- 注: 三级映射 进程PCB–>页目录(可看出数组,首地址在PCB中)—>页表—>物理页面—>内存单元
-
进程:有独立的进程地址空间。有独立的PCB 分配资源的最小单元
-
线程: 有独立的PCB。没有独立的进程地址空间 最小单位的执行
-
ps -Lf pid
获得线程号 LWP
线程共享的资源
- 共享:./text ./data ./rodata ./bsss heap –>全局变量 文件描述符表
- 独享: errno变量 信号屏蔽字 调度优先级 栈空间(内核栈 和 用户栈)
线程控制原语
-
pthread_t pthread_self (void)
获取线程id,- 返回,本线程id
-
检查出错返回:
-
fprintf( stderr ,"...%s..", strerror())
-
在线程中使用
perror
函数可能会导致错误信息不准确或者无法获取到正确的错误信息。这是因为 perror 函数是基于全局变量errno
来输出错误信息的,而在多线程环境中,每个线程都有自己独立的 errno 变量,因此在多线程环境下使用 perror 函数可能会导致错误信息混乱或者无法获取到正确的错误信息。为了避免这种情况,可以使用
strerror
函数来获取特定错误码对应的错误信息,或者使用线程安全的strerror_r
函数来获取线程特定的错误信息。 -
fprintf函数 int fprintf(FILE*stream , const char* format, [argument]..) 函数没有缓冲
-
-
int pthread_creat(pthread_t *tid , const pthread_attr_t *attr , void *(*start_rountn)(void*) , void *arg)
- 参数:
- 参数1,返回参数,表示新创建的子线程
id
- 参数2,线程属性,传 NULL 表示默认属性
- 参数3,子线程回调函数,创建成功,
pthread_creat
函数返回时,该函数会被自动调用 - 参数4,参数3的参数,没有传NULL
-
void pthread_exit (void * retval) //退出当前线程 retval :退出值,无退出值时为NULL int pthread_join (pthread_t thread,void** retval) //回收线程 thread :待回收的线程id retval : 传出参数,回收的线程的退出值 返回值:成功返回0 失败 -1 errno int pthread_detach(pthread_t thread) //设置线程分离 线程终止,会自动清理pcb 无需线程回收 thread 带分离线程id 成功返回 0 int pthread_cancel(pthread_t thread) //杀死一个线程,需要到达取消点(保存点) thread 待杀死的线程 如果,子线程没有到达取消点,那么函数失效 我们可以在程序中手动添加一个取消点 使用 pthread_testcancel() 成功被 函数杀死 返回-1 被pthread_join()回收
线程和进程对比
- 线程控制源于
线程控制原语 | 进程控制原语 |
---|---|
pthread_creat() | fork() |
pthread_self() | getpid() |
pthread_exit() | exit() |
pthread_join() | wait()/waitpid() |
pthread_cancel() | kill() |
pthread_detach() |
线程属性
- 例如设置分离属性
pthread_attr_t attr
创建一个线程属性结构体变量pthread_attr_init (&attr)
初始化线程属性pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED)
设置线程属性为分离态pthread_create(&tid , %attr, tfn, NULL)
创建分离的线程pthread_attr_init(&attr)
销毁线程属性
线程使用注意事项
- 主线程退出其他线程还没有退出 主线程调用
pthread_exit()
- 避免僵尸线程
线程同步
- 锁,是建议锁, 应先拿锁再访问
使用 mutex
(互斥锁,互斥量):
pthread_mutex_t lock()
创建锁pthread_mutex_init(& mutex,NULL)
动态初始化 静态初始化pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER
pthread_mutex_lock()
加锁- 访问共享数据(stdout)
pthread_mutex_unlock()
解锁pthread_mustex_destory
销毁锁
restrict 关键字
- 用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。
读写锁
- 只有一把
- 读共享,写独占。
- 写锁优先级高于读锁
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock ,NULL);
pthread_rwlock_rdlock(&rwlock);
pthread_rwlock_wrlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destory(&rwlock);
条件变量
- 本身不是锁
pthread_cond_init (&cond, NULL)
; //动态初始化pthread_cond_t cond = PIHREAD_COND_INITIALIZER
阻塞等待条件:
pthread_cond_wait(&cond,&mutex)
:
作用: 1)阻塞等待条件变量满足
2)解锁已经加锁成功的信号量(相当于pthread_mutex_un1ock(&mutex)
)
3)当条件满足,函数返回时,重新加锁信号量(相当于,pthread_mutex_1ock(&mutex);
)
pthread_cond_signal()
唤醒阻塞在条件变量上的至少一个线程
pthread_cond_broadcast()
唤醒所有线程
信号量
-
相当于初始化N个互斥量,N表示可以同时访问共享数据区的线程数
-
sem_t sem 定义类型; int sem_init(sem_t *sem,int pshared, unsigned int value); 参数: sem 信号量 pshared 0:用于线程间同步 1:用于进程间同步 value:N值
-
sem_destroy()
-
sem_wait()
一次调用,进行一次 - - 操作, -
sem_post()
进行++操作