linux常见目录:

  • /bin: /usr/bin: 可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。
  • /boot:放置linux系统启动时用到的一些文件,如Linux的内核文件:/boot/vmlinuz,系统引导管理器:/boot/grub。
  • /dev:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,常用的是挂载光驱 mount /dev/cdrom /mnt。
  • /etc:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有 /etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d。
  • /home:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,表示当前用户的家目录,edu 表示用户 edu 的家目录。
  • /lib: /usr/lib: /usr/local/lib系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助。
  • /lost+fount:系统异常产生错误时,会将一些遗失的片段放置于此目录下。
  • /mnt:/media:光盘默认挂载点,通常光盘挂载于 /mnt/cdrom 下,也不一定,可以选择任意位置进行挂载。
  • /opt:给主机额外安装软件所摆放的目录。
  • /proc:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有 /proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/* 等。
  • /root:系统管理员root的家目录。
  • /sbin:/usr/sbin:/usr/local/sbin:放置系统管理员使用的可执行命令,如fdisk、shutdown、mount 等。与 /bin 不同的是,这几个目录是给系统管理员 root使用的命令,一般用户只能”查看”而不能设置和使用。
  • **/tmp**一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下。
  • /srv:服务启动之后需要访问的数据目录,如 www 服务需要访问的网页数据存放在 /srv/www 内。
  • /usr:应用程序存放目录,/usr/bin 存放应用程序,/usr/share 存放共享数据,/usr/lib 存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local: 存放软件升级包。/usr/share/doc: 系统说明文件存放目录。/usr/share/man: 程序说明文件存放目录。
  • /var:放置系统执行过程中经常变化的文件,如随时更改的日志文件 /var/log,/var/log/message:所有的登录文件存放目录,/var/spool/mail:邮件存放的目录,/var/run:程序或服务启动后,其PID存放在该目录下。

快捷键:

移到头部尾部:ctrl + a/e

删除前后字符或所有字符:ctrl + h/d ,ctrl + u/k

窗口/标签:ctrl+shit+ N/Q T/W

文件

man 中各个 section 意义如下:

deng@itcast:~$ man man

1).Standard commands(标准命令)

2).System calls(系统调用,如open,write)

3).Library functions(库函数,如printf,fopen)

4).Special devices(设备文件的说明,/dev下各种设备)

5).File formats(文件格式,如passwd)

6).Games and toys(游戏和娱乐)

7).Miscellaneous(杂项、惯例与协定等,例如Linux档案系统、网络协定、ASCII 码;environ全局变量)

8).Administrative Commands(管理员命令,如ifconfig)

通常,Unix/Linux系统中常用的文件类型有7种:普通文件、目录文件、设备文件、管道文件、链接文件和套接字。

命令:

du:查看指定目录或文件

df:查看文件系统占用

find:-name/-size/-type

grep:-r搜索目录, -v求反

tar、gzip,unzip

chmod u/g/o + +/-/=

chown

chgrp

ln:-s—》readlink

gcc常用选项

选项 作用
-o file 指定生成的输出文件名为file
-E 只进行预处理
-S(大写) 只进行预处理和编译
-c(小写) 只进行预处理、编译和汇编
-v / –version 查看gcc版本号
-g 包含调试信息
-On n=0~3 编译优化,n越大优化得越多
-Wall 提示更多警告信息
-D 编译时定义宏

静态、动态链接

gcc -static test.c -o test

静态库:

ar -rcs libxxx.a x.o xx.o xxx.o

  • r更新
  • c创建
  • s建立索引

gcc test.c -L./ -I./ -lxxx -o test

  • -L:表示要连接的库所在目录
  • -I./: I(大写i) 表示指定头文件的目录为当前目录
  • -l(小写L):指定链接时需要的库,去掉前缀和后缀

动态库:

-fPIC 编译生成与地址无关

gcc -shared xxx.o xx.o x.o -o libxxx.so

查看库函数:nm libxxx.so

ldd 查看依赖

gcc test.c -L./ -I./ -lxxx -o test编译时需指定绝对路径

  1. 拷贝到/lib或/usr/lib
  2. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:PATH
  3. echo /home/your_path >> ld.sp.conf && ldconfig
  4. ln -s /home/your_path/libxxx.so /lib/libxxx.so

makefile:

make [ -f file ][ options ][ targets ]

  • -v: 显示make工具的版本信息
  • -w: 在处理makefile之前和之后显示工作路径
  • -C dir:读取makefile之前改变工作路径至dir目录
  • -n:只打印要执行的命令但不执行
  • -s:执行但不显示执行的命令

原理:

分析目标和依赖关系;根据依赖自底向上执行;修改时间比目标新则更新;如不依赖任何条件则执行对应命令

变量:

CC = gcc #arm-linux-gcc

CPPFLAGS : C预处理的选项 如:-I

CFLAGS: C编译器的选项 -Wall -g -c

LDFLAGS : 链接器选项 -L -l

$@: 表示规则中的目标

$<: 表示规则中的第一个条件

$^: 表示规则中的所有条件, 组成一个列表, 以空格隔开,如果这个列表中有重复的项则消除重复项。

%:匹配一个模式规则

1
2
3
4
5
6
7
OBJS=test.o add.o sub.o mul.o div.o
TARGET=test
$(TARGET):$(OBJS)
gcc $(OBJS) -o $(TARGET)

%.o:%.c
gcc -c $< -o $@

函数:都有返回会值

  1. wildcard – 查找指定目录下的指定类型的文件

src = $(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src

  1. patsubst – 匹配替换

obj = $(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o

伪目标:

.PHONY:clean

  • “-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
  • “@”不显示命令本身,只显示结果。如:“@echo clean done”

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── bin
├── inc
│ ├── add.h
│ ├── div.h
│ ├── mul.h
│ └── sub.h
├── Makefile
├── obj
└── src
├── add.c
├── div.c
├── mul.c
├── sub.c
└── test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CC = gcc
CFLAGS = -Iinc
SRCS = $(wildcard src/*.c)
OBJS = $(patsubst src/%.c, obj/%.o, $(SRCS))

bin/test: $(OBJS)
$(CC) $(CFLAGS) $^ -o $@

obj/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -f $(OBJS) bin/test

.PHONY: all clean

C库IO函数

1527650554264

错误号:

1
2
/usr/include/asm-generic/errno-base.h
/usr/include/asm-generic/errno.h

文件IO

1
2
3
4
5
6
7
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
off_t lseek(int fd, off_t offset, int whence);
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节

文件操作相关:

stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能:
获取文件状态信息
stat和lstat的区别:
当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;
而stat返回的是该链接指向的文件的信息。

struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
1
2
3
4
5
6
7
S_ISREG()	010 普通文件
S_ISDIR() 004 目录
S_ISCHR() 002 字符设备
S_ISBLK() 006 块设备
S_ISFIFO() 001 管道
S_ISLNK() 012 符号链接
S_ISSOCK() 014 套接字

access

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>
int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数:
pathname:文件名
mode:文件权限,4种权限
R_OK: 是否有读权限
W_OK: 是否有写权限
X_OK: 是否有执行权限
F_OK: 测试文件是否存在

修改文件权限函数

1
2
int chmod(const char *pathname, mode_t mode);
int chown(const char *pathname, uid_t owner, gid_t group);

truncate函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
功能:修改文件大小
参数:
path:文件文件名字
length:指定的文件大小
a)比原来小, 删掉后边的部分
b)比原来大, 向后拓展
返回值:
成功:0
失败:-1

link函数

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
oldpath:源文件名字
newpath:硬链接名字
返回值:
成功:0
失败:-1

symlink函数

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

int symlink(const char *target, const char *linkpath);
功能:创建一个软链接
参数:
target:源文件名字
linkpath:软链接名字
返回值:
成功:0
失败:-1

readlink函数

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
pathname:软连接名
buf:存放软件对应的文件名
bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
成功:>0,读到buf中的字符个数
失败:-1

unlink函数

1
2
3
4
5
6
7
8
9
#include <unistd.h>

int unlink(const char *pathname);
功能:删除一个文件(软硬链接文件)
参数:
pathname:删除的文件名字
返回值:
成功:0
失败:-1

rename函数

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败:-1

fcnlt函数

1
2
3
4
5
6
7
8
9
10
11
12
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */);
功能:改变已打开的文件性质,fcntl针对描述符提供控制。
参数:
fd:操作的文件描述符
cmd:操作方式
arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值:
成功:返回某个其他值
失败:-1

fcntl函数有5种功能:

  1. 复制一个现有的描述符(cmd=F_DUPFD)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)

目录相关

1
2
3
4
5
6
7
8
9
10
11
12
13
char *getcwd(char *buf, size_t size);
int chdir(const char *path);
DIR *opendir(const char *name);
int closedir(DIR *dirp);
struct dirent *readdir(DIR *dirp);
struct dirent
{
ino_t d_ino; // 此目录进入点的inode
off_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度, 不包含NULL 字符
unsigned char d_type; // d_type 所指的文件类型
char d_name[256]; // 文件名
};
取值 含义
DT_BLK 块设备
DT_CHR 字符设备
DT_DIR 目录
DT_LNK 软链接
DT_FIFO 管道
DT_REG 普通文件
DT_SOCK 套接字
DT_UNKNOWN 未知

时间相关函数

utime

time

1
2
3
4
5
6
7
8
9
10
11
12
13
char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

time_t mktime(struct tm *tm);

PCB

进程运行时,内核为进程每个进程分配一个PCB(进程控制块),维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

在 /usr/src/linux-headers-xxx/include/linux/sched.h 文件中可以查看struct task_struct 结构体定义:

  • 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
  • 进程的状态,有就绪、运行、挂起、停止等状态。
  • 进程切换时需要保存和恢复的一些CPU寄存器。
  • 描述虚拟地址空间的信息。
  • 描述控制终端的信息。
  • 当前工作目录(Current Working Directory)。
  • umask掩码。
  • 文件描述符表,包含很多指向file结构体的指针。
  • 和信号相关的信息。
  • 用户id和组id。
  • 会话(Session)和进程组。
  • 进程可以使用的资源上限(Resource Limit)。

进程状态

在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态

在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态

1527908066890

①TASK_RUNNING:进程正在被CPU执行。当一个进程刚被创建时会处于TASK_RUNNABLE,表示己经准备就绪,正等待被调度。

  ②TASK_INTERRUPTIBLE(可中断):进程正在睡眠(也就是说它被阻塞)等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒比如给一个TASK_INTERRUPTIBLE状态的进程发送SIGKILL信号,这个进程将先被唤醒(进入TASK_RUNNABLE状态),然后再响应SIGKILL信号而退出(变为TASK_ZOMBIE状态),并不会从TASK_INTERRUPTIBLE状态直接退出。

  ③TASK_UNINTERRUPTIBLE(不可中断):处于等待中的进程,待资源满足时被唤醒,但不可以由其它进程通过信号或中断唤醒。由于不接受外来的任何信号,因此无法用kill杀掉这些处于该状态的进程。而TASK_UNINTERRUPTIBLE状态存在的意义就在于内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程,于是原有的流程就被中断了,这可能使某些设备陷入不可控的状态。处于TASK_UNINTERRUPTIBLE状态一般总是非常短暂的,通过ps命令基本上不可能捕捉到。

  ④TASK_ZOMBIE(僵死):表示进程已经结束了,但是其父进程还没有调用wait4或waitpid()来释放进程描述符。为了父进程能够获知它的消息,子进程的进程描述符仍然被保留着。一旦父进程调用了wait4(),进程描述符就会被释放。

  ⑤TASK_STOPPED(停止):进程停止执行。当进程接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。当接收到SIGCONT信号,会重新回到TASK_RUNNABLE

stat中的参数意义如下:

参数 含义
D 不可中断 Uninterruptible(usually IO)
R 正在运行,或在队列中的进程
S(大写) 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组

ps查看进程详细状态

top命令用来动态显示运行中的进程

kill [-signal] pid;一般信号9,SIGTERM

killall通过进程名字杀死进程

进程相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main()
{
pid_t pid, ppid, pgid;
pid = getpid();
printf("pid = %d\n", pid);
ppid = getppid();
printf("ppid = %d\n", ppid);
pgid = getpgid(pid);
printf("pgid = %d\n", pgid);
return 0;
}

pid_t fork(void);

#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
options : options 提供了一些额外的选项来控制 waitpid()。
0:同 wait(),阻塞父进程,等待子进程退出。
WNOHANG:没有任何已经结束的子进程,则立即返回。
WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)

Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。共享同一个地址空间

fork()函数继承了整个进程的地址空间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等,代价巨大

fork之后父子进程共享文件,fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

父子进程各自的地址空间是独立的

gdb调试多进程

使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

  • set follow-fork-mode child 设置gdb在fork之后跟踪子进程。
  • set follow-fork-mode parent 设置跟踪父进程(默认)。

注意,一定要在fork函数调用之前设置才有效。

_exit直接调用 _exit系统调用,exit()是标准库函数,先atexit注册退出处理函数,然后刷新IO缓存,关文件描述符,再调用 _ exit

wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。

注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

exec函数族

exec 只是用另一个新程序替换了当前进程的正文、数据、堆和栈段(进程替换)。

1
2
3
4
5
6
7
8
9
10
11
#include <unistd.h>
extern char **environ;

int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

int execve(const char *filename, char *const argv[], char *const envp[]); //系统调用

进程间通信

无名管道:

管道中有数据,read返回实际读到的字节数。

管道中无数据:

管道写端被全部关闭,read返回0 (相当于读到文件结尾)

写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)

管道读端没有全部关闭:

管道已满,write阻塞。

管道未满,write将数据写入,并返回实际写入的字节数。

可以使用ulimit -a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。

1
2
3
4
5
6
7
8
int pipe(int pipefd[2]);
long fpathconf(int fd, int name);
功能:该函数可以通过name参数查看不同的属性值
参数:
fd:文件描述符
name:
_PC_PIPE_BUF,查看管道缓冲区大小
_PC_NAME_MAX,文件名字字节数的上限

有名管道

以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

特点

  1. FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。
  2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
  3. FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。
1
2
mkfifo <name>
int mkfifo(const char *pathname, mode_t mode);

共享存储映射

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
addr : 指定映射的起始地址, 通常设为NULL, 由系统指定
length:映射到内存的文件长度
prot: 映射区的保护方式, 最常用的 :
a) 读:PROT_READ
b) 写:PROT_WRITE
c) 读写:PROT_READ | PROT_WRITE
flags: 映射区的特性, 可以是
a) MAP_SHARED : 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符, 代表要映射的文件。
offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射
返回值:
成功:返回创建的映射区首地址
失败:MAP_FAILED宏
int munmap(void *addr, size_t length);

当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)

当映射文件大小为0时,不能创建映射区。

文件偏移量必须为4K的整数倍

**MAP_ANONYMOUS (或MAP_ANON)**。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int len = 1<<12;
void* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
if(ptr == MAP_FAILED)
{
perror("mmap error");
exit(1);
}

pid_t pid = fork();
if(pid > 0)
{
strcpy((char*)ptr, "hello world!");
wait(NULL);
}
else if(pid == 0)
{
sleep(1);
printf("%s\n", (char*)ptr);
}
int ret = munmap(ptr, len);
if(ret == -1)
{
perror("munmap error");
eixt(1);
}

信号

信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式

信号的特点

  • 简单
  • 不能携带大量信息
  • 满足某个特设条件才发送

一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数

kill -l常规信号

编号 信号 对应事件 默认动作
1 SIGHUP 用户退出shell时,由该shell启动的所有进程将收到这个信号 终止进程
2 SIGINT 当用户按下了**<Ctrl+C>**组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号 终止进程
3 SIGQUIT 用户按下**<ctrl+\ >**组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号 终止进程
4 SIGILL CPU检测到某进程执行了非法指令 终止进程并产生core文件
5 SIGTRAP 该信号由断点指令或其他 trap指令产生 终止进程并产生core文件
6 SIGABRT 调用abort函数时产生该信号 终止进程并产生core文件
7 SIGBUS 非法访问内存地址,包括内存对齐出错 终止进程并产生core文件
8 SIGFPE 在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误 终止进程并产生core文件
9 SIGKILL 无条件终止进程。本信号不能被忽略,处理和阻塞 终止进程,可以杀死任何进程
10 SIGUSE1 用户定义的信号。即程序员可以在程序中定义并使用该信号 终止进程
11 SIGSEGV 指示进程进行了无效内存访问(段错误) 终止进程并产生core文件
12 SIGUSR2 另外一个用户自定义信号,程序员可以在程序中定义并使用该信号 终止进程
13 SIGPIPE Broken pipe向一个没有读端的管道写数据 终止进程
14 SIGALRM 定时器超时,超时的时间 由系统调用alarm设置 终止进程
15 SIGTERM 程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号 终止进程
16 SIGSTKFLT Linux早期版本出现的信号,现仍保留向后兼容 终止进程
17 SIGCHLD 子进程结束时,父进程会收到这个信号 忽略这个信号
18 SIGCONT 如果进程已停止,则使其继续运行 继续/忽略
19 SIGSTOP 停止进程的执行。信号不能被忽略,处理和阻塞 为终止进程
20 SIGTSTP 停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号 暂停进程
21 SIGTTIN 后台进程读终端控制台 暂停进程
22 SIGTTOU 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生 暂停进程
23 SIGURG 套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达 忽略该信号
24 SIGXCPU 进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程 终止进程
25 SIGXFSZ 超过文件的最大长度设置 终止进程
26 SIGVTALRM 虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间 终止进程
27 SGIPROF 类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间 终止进程
28 SIGWINCH 窗口变化大小时发出 忽略该信号
29 SIGIO 此信号向进程指示发出了一个异步IO事件 忽略该信号
30 SIGPWR 关机 终止进程
31 SIGSYS 无效的系统调用 终止进程并产生core文件
34~64 SIGRTMIN ~ SIGRTMAX LINUX的实时信号,它们没有固定的含义(可以由用户自定义) 终止进程

每个信号必备4要素,分别是:

1)编号 2)名称 3)事件 4)默认处理动作

可通过man 7 signal查看帮助文档获取:

标准信号中,有一些信号是有三个“Value”,第一个值通常对alpha和sparc架构有效,中间值针对x86、arm和其他架构,最后一个应用于mips架构。一个‘-’表示在对应架构上尚未定义该信号。

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

信号相关函数

1
2
3
4
5
6
7
8
int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号(不一定杀死)
参数:
pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为pid的进程。
pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
1
2
int raise(int sig);
功能:给当前进程发送指定信号(自己给自己发),等价于 kill(getpid(), sig)
1
void abort(void);
1
2
3
4
unsigned int alarm(unsigned int seconds);
功能:
设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器alarm(0),返回旧闹钟余下秒数。

定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸……无论进程处于何种状态,alarm都计时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sys/time.h>

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
功能:
设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
which:指定定时方式
a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间
c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
new_value:struct itimerval, 负责设定timeout时间
struct itimerval {
struct timerval it_interval; // 闹钟触发周期
struct timerval it_value; // 闹钟触发时间
};
struct timeval {
long tv_sec; // 秒
long tv_usec; // 微秒
}
itimerval.it_value: 设定第一次执行function所延迟的秒数
itimerval.it_interval: 设定以后每几秒执行function

old_value: 存放旧的timeout值,一般指定为NULL

信号集

概述

未决状态:没有被处理

递达状态:信号被处理了

Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集

阻塞信号集(信号屏蔽字)

将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。

未决信号集

信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。

信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。


这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号集进行修改。

自定义信号集函数

1
2
3
4
5
6
7
#include <signal.h>  

int sigemptyset(sigset_t *set); //将set集合置空
int sigfillset(sigset_t *set)//将所有信号加入set集合
int sigaddset(sigset_t *set, int signo); //将signo信号加入到set集合
int sigdelset(sigset_t *set, int signo); //从set集合中移除signo信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
sigset_t set;
int ret = 0;
sigemptyset(&set);
ret = isgismember(&set, SIGINT);
if(ret == 0) printf("SIGINT is not a member of set \nret = %d\n", ret);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
ret = sigismember(&set, SIGINT);
if(ret == 1) printf("SIGINT is a member of set \nret = %d\n", ret);
sigdelset(&set, SIGQUIT);
ret = sigismember(&set, SIGQUIT);
if (ret == 0) printf("SIGQUIT is not a member of set \nret = %d\n", ret);
return 0;
}

阻塞信号集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。

参数:
how : 信号阻塞集合的修改方法,有 3 种情况:
SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于 mask = mask|set
SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set
SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set
set : 要操作的信号集地址。
setNULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
oldset : 保存原先信号阻塞集地址

int sigpending(sigset_t *set);
功能:读取当前进程的未决信号集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
itn main()
{
sigset_t myset, old;
sigemptyset(&myset);
sigaddset(&myset, SIGINT);
sigaddset(&myset, SIGQUIT);
sigaddset(&myset, SIGKILL);
sigprocmask(SIG-BLOCK, &myset, &old);

sigset_t pend;
int i = 0;
while(1)
{
sigpending(&pend);
for(int i = 1; i < 32; ++i)
{
if(sigismember(&pend, i)) printf("1");
else if(sigismember(&pend, i) == 0) printf("0");
}
printf("\n");
sleep(1);
i++;

if(i > 10)
{
sigprocmask(SIG_SETMASK, &old, NULL);
}
}
return 0;
}

信号捕捉

SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

内核实现信号捕捉过程:

1527931072795

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void(*sa_handler)(int); //旧的信号处理函数指针
void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
sigset_t sa_mask; //信号阻塞集
int sa_flags; //信号处理的方式
void(*sa_restorer)(void); //已弃用
};

int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval
{
int sival_int;
void *sival_ptr;
};

SIGCHLD信号避免僵尸进程

  1. 子进程终止时
  2. 子进程接收到SIGSTOP信号停止时
  3. 子进程处在停止态,接受到SIGCONT后唤醒时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
pid_t pid;
signal(SIGCHLD, SIG_IGN);
pid = fork();
if(pid < 0)
{
perror("fork error:");
exit(1);
}
else if(pid == 0)
{
printf("I am child process,pid id %d.I am exiting.\n", getpid());
exit(0);
}
else if(pid > 0)
{
sleep(2);
printf("I am father, i am exited\n\n");
system("ps -ef | grep defunct"); // 查看有没有僵尸进程
}
return;
}

使用dup和exce实现:ps aux|grep bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(void)
{
int ret = 01;
int fd[2] = {0};
pid_t pid = -1;
ret = pipe(fd);
if(-1 == ret)
{
perror("pipe");
goto err0;
}
pid = fork();
if(-1 == pid)
{
perror("fork");
goto err0;
}
if(0 == pid)
{
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
exit(0);
}
else
{
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("grep", "grep", "bash","--color=auto",NULL);
}
return 0;
err0:
return 1;
}

守护进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
* time_t rawtime;
* time ( &rawtime ); --- 获取时间,以秒计,从1970年1月一日起算,存于rawtime
* localtime ( &rawtime ); //转为当地时间,tm 时间结构
* asctime() // 转为标准ASCII时间格式:
*/
void write_time(int num)
{
time_t rawtime;
struct tm * timeinfo;
// 获取时间
time(&rawtime);
#if 0
// 转为本地时间
timeinfo = localtime(&rawtime);
// 转为标准ASCII时间格式
char *cur = asctime(timeinfo);
#else
char* cur = ctime(&rawtime);
#endif

// 将得到的时间写入文件中
int fd = open("/home/edu/timelog.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
if (fd == -1)
{
perror("open error");
exit(1);
}
// 写文件
int ret = write(fd, cur, strlen(cur) + 1);
if (ret == -1)
{
perror("write error");
exit(1);
}
// 关闭文件
close(fd);
}

int main(int argc, const char* argv[])
{
pid_t pid = fork();
if (pid == -1)
{
perror("fork error");
exit(1);
}

if (pid > 0)
{
// 父进程退出
exit(1);
}
else if (pid == 0)
{
// 子进程
// 提升为会长,同时也是新进程组的组长
setsid();

// 更改进程的执行目录
chdir("/home/edu");

// 更改掩码
umask(0022);

// 关闭文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

// 注册信号捕捉函数
//先注册,再定时
struct sigaction sigact;
sigact.sa_flags = 0;
sigemptyset(&sigact.sa_mask);
sigact.sa_handler = write_time;
sigaction(SIGALRM, &sigact, NULL);

// 设置定时器
struct itimerval act;
// 定时周期
act.it_interval.tv_sec = 2;
act.it_interval.tv_usec = 0;
// 设置第一次触发定时器时间
act.it_value.tv_sec = 2;
act.it_value.tv_usec = 0;
// 开始计时
setitimer(ITIMER_REAL, &act, NULL);

// 防止子进程退出
while (1);
}

return 0;
}

线程

线程号只在它所属的进程环境中有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pthread_t pthread_self(void);

int pthread_equal(pthread_t t1, pthread_t t2);

int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );

int pthread_join(pthread_t thread, void **retval);

int pthread_detach(pthread_t thread);

void pthread_exit(void *retval);

int pthread_cancel(pthread_t thread);

线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。

杀死线程也不是立刻就能完成,必须要到达取消点。

取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write….. 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。

线程属性

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
} pthread_attr_t;
1
2
3
4
5
6
7
8
9
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
attr:已初始化的线程属性
detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)

这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。

要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。

设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#define SIZE 0x100000
void*th_func(void*arg)
{
while(1)
{
sleep(1);
}
}
int main()
{
pthread_t tid;
int err. detachstate, i = 1;

pthread_attr_t attr;
size_t stacksize;
void *stackaddr;

pthread_attr_init(&attr);
pthread_attr_getstack(&attr, &stackaddr, &stacksize);
pthread_attr_getdetachstate(&attr, &detachstate);

if(detachstate == PTHREAD_CREATE_DETACHED)
{
printf("thread is detached\n");
}
else if(detachstate == PTHREAD_CREATE_JOINABLE)
{
printf("thread is joinable\n");
}
else
{
printf("thread is unknow\n");
}
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

while(1)
{
stackaddr = malloc(SIZE);
if(stackaddr == NULL)
{
printf("malloc failed\n");
exit(1);
}
stacksize = SIZE;
pthread_attr_setstack(&attr, stackaddr, stacksize);
err = pthread_create(&tid, &attr, th_func, NULL);
if(err!= 0)
{
printf("create thread failed\n");
exit(1);
}
printf("create thread %d\n", i++);
}
pthread_attr_destroy(&attr);;
return 0;
}

线程同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
参数:
mutex:互斥锁地址。类型是 pthread_mutex_t
attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL
可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

死锁引起的原因

  • 竞争不可抢占资源引起死锁
  • 竞争可消耗资源引起死锁
  • 进程推进顺序不当引起死锁

预防死锁的方法

*破坏请求和保持条件*

协议1:

所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

协议2:

允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。

*破坏不可抢占条件*

当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必须释放已经保持的所有资源,以后需要时再重新申请。

*破坏循环等待条件*

对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。

读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
以阻塞方式在读写锁上获取读锁(读锁定)。
如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。
线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
 用于尝试以非阻塞的方式来在读写锁上获取读锁。
 如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

pthread_rwlock_t rwlock;
int num = 1;

void *func1(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read num first==%d\n", num);
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
}

void *func2(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read num second==%d\n", num);
pthread_rwlock_unlock(&rwlock);
usleep(2000);
}
}
void *func3(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write num first==%d\n", num);
pthread_rwlock_unlock(&rwlock);
usleep(2000);
}
}
void *func4(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write num second==%d\n", num);
pthread_rwlock_unlock(&rwlock);
usleep(1000);
}
}
int main()
{
pthread_t rd1, rd2, wr1, wr2;
pthread_rwlock_init(&rwlock, NULL);

pthread_create(&rd1, NULL, func1, NULL);
pthread_create(&rd2, NULL, func2, NULL);
pthread_create(&wr1, NULL, func3, NULL);
pthread_create(&wr2, NULL, func4, NULL);

pthread_join(rd1, NULL);
pthread_join(rd2, NULL);
pthread_join(wr1, NULL);
pthread_join(wr2, NULL);

pthread_rwlock_destroy(&rwlock);
return 0;
}

条件变量

1
2
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
1
2
3
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct *restrict abstime);

a) 阻塞等待条件变量cond(参1)满足

b) 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
a) b) 两步为一个原子操作。

c) 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
1
2
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

生产者消费者模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>

typedef struct node
{
int val;
struct node *next;
}Node;

Node *head = NULL;

pthread_mutex_t lock;
pthread_cond_t cond;

void *producer(void *arg)
{
while(1)
{
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->val = rand() % 1000;
pthread_mutex_lock(&lock);
new_node->next = head;
head = new_node;
printf("======produce: %lu, %d\n", pthread_self(), new_node->val);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
sleep(rand() % 3);
}
return NULL;
}

void *consumer(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);
while(head == NULL)
{
pthread_cond_wait(&cond, &lock);
}
Node *tmp = head;
head = head->next;
printf("------consume: %lu, %d\n", pthread_self(), tmp->val);
free(tmp);
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main()
{
pthread_t tid1, tid2;
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&cond, NULL);
srand((unsigned long)time(NULL));
pthread_create(&tid1, NULL, producer, NULL);
pthread_create(&tid2, NULL, consumer, NULL);

pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}

相较于mutex而言,条件变量可以减少竞争。

如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。

有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

信号量

1
2
3
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
int sem_destroy(sem_t *sem);

P操作

1
2
3
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

V操作

1
int sem_post(sem_t *sem);

哲学家就餐问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex[5];

void* dinner(void* arg)
{
int i = (int)arg;
int left = i;
int right = (i + 1) % 5;
while(1)
{
pthread_mutex_lock(&mutex[left]);
if(pthread_mutex_trylock(&mutex[right]) == 0)
{
printf("%c is eating\n" , i+'A');
pthread_mutex_unlock(&mutex[left]);
}
pthread_mutex_unlock(&mutex[right]);
sleep(rand() % 5);
}
}
int main()
{
pthread_t tid[5];
int i = 0;
for(i = 0; i < 5; i++)
{
pthread_mutex_init(&mutex[i], NULL);
}
for(i = 0; i < 5; i++)
{
pthread_create(&tid[i], NULL, dinner, (void*)i);
}
for(i = 0; i < 5; i++)
{
pthread_join(tid[i], NULL);
}
for(i = 0; i < 5; i++)
{
pthread_mutex_destroy(&mutex[i]);
}
return 0;
}

``