进程
MMU

进程控制块PCB
进程控制块PCB的结构体形式:task_struct。

虚拟地址空间的信息描述为虚拟地址到物理地址的映射。MMU来维护。
umask掩码:保护文件权限。
ulimit -a 能查看linux资源上限
环境变量
以用户为单位设置环境变量。
- PATH
- SHELL 当前SHELL
- TERM 当前终端类型
- HOME
- LANG
类似于命令行参数。

getenv 获取环境变量
setenv设置环境变量
unsetenv取消环境变量
进程控制
fork()

创建的是子进程。通过返回的pid来判断是否在子进程中。返回0时为子进程,父进程中返回子进程的pid。
getpid 获取 pid
getppid 获取父亲 pid
创建N个进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int forkN(int n){
printf("I am the father %d\n", getpid());
for(int i = 0; i < n; i++){
int pid = fork();
if(pid == -1){
perror("fork error!");
exit(1);
}
else if(pid == 0){
printf("I am the %d child %u, my father is %u\n", i + 1, getpid(), getppid());
break;
}
}
return 0;
}
|
进程共享
刚fork完时,父子进程中
全局变量、data、.text、栈、堆、环境变量、用户ID、目录、信号处理方式都是相同的。(0-3G地址相同)
不同在于进程id、fork返回值、父进程id、进程运行时间、定时器(一个进程有一个)、未决信号集。(PCB不一样)
fork完之后,父子进程之间遵循读时共享,写时复制的原则,节省内存开销。
注意父子进程之间不能通过全局变量共享数据。(写时复制)
父子进程间共享的点:
- 文件描述符(多个进程对同一个文件操作)
- mmap建立的映射区(进程间通信)
gdb调试多进程程序
1
2
|
set follow-fork-mod child
set follow-fork-mod parent
|
exec类函数
让程序执行一个进程(原进程的用户空间和代码完全被新程序替换)。
注意此时并不是创建一个新进程,而是原进程的用户空间和代码全部被替换。

重点execl和execlp。
保存 ps 结果到 out
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
int ps()
{
int fd = open("out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if(fd == -1){
perror("open file:");
exit(1);
}
dup2(fd, STDOUT_FILENO); //把out文件的描述符复制到std::out
int res = execlp("ps", "ps", "aux", NULL);
if(res == -1)perror("exec error:");
exit(1);
close(fd);
return 0;
}
|
exec函数只在出错的时候返回-1。
回收子进程
孤儿进程
父进程先于子进程结束,子进程则为孤儿进程,子进程父进程变为init进程(孤儿院),父进程的pid变为1。
僵尸进程
进程终止了,但是父进程没有回收。子进程PCB残留在内核中,成为僵尸进程。

这里的父进程一直不停止,但是子进程的PCB一直不回收。

wait waitpid 解决僵尸函数
wait有三个功能
- 阻塞等待子进程退出。
- 回收子进程残留资源。
- 获取子进程结束状态。
一次wait只能回收一个进程,回收先结束的进程。
返回为pid时,回收成功,-1时,没有子进程。

wstatus为传出参数:

kill -l可以查看信号结果。
waitpid函数更灵活一些,指定pid回收,且可以不阻塞。

pid为-1时,回收任意存在的子进程。

0 阻塞 WNOHANG 非阻塞,需要轮询。

总结:

IPC进程间通信
常用方法:
- 管道(最简单)
- 信号(开销最小)
- 共享映射区(无血缘关系)(mmap)
- 本地套接字(最稳定)
管道 pipe
fifo(有名管道,非血缘关系间),pipe(匿名管道)
最简单的想法:通过文件实现进程间通信。抽象成为管道。
管道实际上为内核缓冲区,为伪文件。
一个管道有两个描述符,一个为读一个为写,默认为8k的缓冲区。
内部采用的是循环队列。


返回两个文件描述符,读和写。
简单的管道示例:
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
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int pipeTest(){
int fd[2]; //两个文件描述符
int ret = pipe(fd);
if(ret == -1){
perror("pipe error:");
exit(1);
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}else if(pid == 0){ //child read
close(fd[1]);
char buf[1024];
ret = read(fd[0], buf, sizeof(buf));
if(ret == 0){
printf("read done\n");
}
write(STDOUT_FILENO, buf, ret);
}else{ //father write
close(fd[0]);
sleep(1);
char * str = "hello pipe\n";
ret = write(fd[1], str, strlen(str));
}
}
|
mmap 共享内存

借助共享内存存放磁盘文件。这样可以通过指针访问磁盘文件。
- addr:映射区的首地址 ,内核自动指定(直接传NULL)
- length:映射区大小(文件大小)
- prot:映射区的权限
- PROT_READ
- PROT_WRITE
- PROT_READ | PROTWRITE
- flags:标志位参数
返回创建映射区的首地址。失败时返回MAP_FIALED。

- fd:文件描述符
- offset:映射文件的偏移(截取文件一部分)

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
|
#include<stdio.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
int mmapTest(){
char* p = NULL;
int fd = open("text", O_RDWR | O_CREAT, 0644);
if(fd < 0){
perror("open fail:");
exit(1);
}
int len = 10; //size
if( ftruncate(fd, len) == -1){
perror("ftruncate"); //拓展文件
exit(1);
}
p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
perror("MAP FAILED");
exit(1);
}
strcpy(p, "abc"); //write to mmap
int res = close(fd);
if(res == -1){
perror("close fail");
exit(1);
}
res = munmap(p, len);
if(res == -1){
perror("unmap fail");
exit(1);
}
return 0;
}
|
注意事项:
- 不能创建一个大小为0的映射区(malloc可以)
- 注意munmap释放的必须是以前的首指针。
- 映射区的权限应小于等于实际文件。
- 映射区创建的时候需要读取文件权限。
- offset需要4k的整数倍(一个页的大小)。 ****
- 文件描述符可以先关闭。

mmap 父子之间通信
参数应该为MAP_SHARED。

无血缘也相似,只要对同一个文件调用mmap就行。
读写两边都munmap()。
把映射区当数组看待。
mmap匿名映射
第三个参数位或MAP_ANONYMOUS(MAP_ANON)
。此时文件描述符填-1。注意仅在Linux下有,类Unix下无。
类Unix下,应该打开/dev/zero文件来代替。

fifo和匿名映射的区别:fifo不可以重复读。
strace命令
可以查看程序调用的资源。
阶段性练习
Linux系统编程-实践练习
多文件拷贝
实现方法:文件分成多份,让多个进程去复制各个部分。
简单 shell


本地聊天室功能


使用fcntl()函数把输入改造为非阻塞的。
Linux fcntl函数设置阻塞与非阻塞
信号
区别于信号量。
简单,不能携带大量信息,满足某个条件才会发送。
发送通过内核。程序收到信号后会停止下来处理信号(类似于时钟中断)。但是是通过软件来实现,也称为软中断。
软中断的延时非常大,但是对于用户来说不易察觉。CPU可以察觉。
四要素:
信号的传输过程

未决信号集代表了未处决的信号。

阻塞信号集(信号屏蔽字)代表了处于阻塞态的信号。
两者都处于PCB中。都是set。

当信号屏蔽字为1时,该位的信号将会保留下来,不能被处理。
信号的处理动作:
- 默认处理动作
- 忽略(丢弃)
- 捕捉(捕捉后调用用户处理函数(发送方))


信号编号:kill -l man 7 signal

9和19号不允许被捕捉或阻塞。
信号的产生
产生方法:
- 按键
- Ctrl + c : 2)SIGINT(终止) Interrupt
- Ctrl + z: 20)SIGTSTOP(终端程序暂停) Stop
- Ctrl + \: 3)SIGQUIT(退出) Quit
- 系统调用
- 软件
- 硬件异常
- /0 (8)SIGFPE(浮点数例外)
- 非法访问内存 (11)SIGSEGV(段错误)
- 总线错误 (7)SIGBUS
- 命令调用
- killl
kill -19 pid
发送19信号
- alarm
- setitimer,
kill 函数

raise 与 abort 函数

alarm 函数
每个进程只有一个定时器。
指定需要的second后,内核会给当前进程发送(14)SIGALRM,默认行为为终止进程。
返回值为上一次alarm的剩余时间。

另外可以time ./app查看运行时间。
setitimer
可以取代alarm,精确到微秒,也可以周期性定时。

which函数代表如何定时(自然定时还是用户空间还是用户加内核)。

结构体看man page。
信号集操作
信号集设定
sigset_t 使用unsigned long构成的set(8字节)。

sigprocmask 函数
把自定义的信号set导入到阻塞集。


sigpending 函数
读取当前进程的未决信号集。

示例:打印未决信号集
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
|
#include <stdio.h>
#include <unistd.h>
#include<signal.h>
#include <stdlib.h>
void printsig(sigset_t *set){
for(int i = 1; i < 32; i++){
printf("%d", sigismember(set, i));
}printf("\n");
}
int main()
{
sigset_t myset, oldset, pend;
sigemptyset(&myset);
sigaddset(&myset, SIGQUIT);
int res = sigprocmask(SIG_BLOCK, &myset, &oldset);
if(res == -1){
perror("sig error");
exit(1);
}
sigpending(&pend);
printsig(&pend);
raise(SIGQUIT);
sigpending(&pend);
while(1){
sleep(1);
printsig(&pend);
}
return 0;
}
|
信号的捕捉
signal 函数
使用signal函数可以捕捉到信号,调用自定义的处理函数。

sigaction 函数

其中sigaction结构为

第二个参数不管。
sigset_t 定义handler运行期间的信号屏蔽字。
sa_flags 0时为默认。


捕捉原理

竞态条件
pause函数:
可以造成进程主动挂起,等待信号唤醒。注意先要对信号进行捕捉。

注意这里返回的是-1。
使用sigaction和pause实现sleep:
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
|
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void catchSignal(int signo){
;
}
int mySleep(unsigned int seconds){
struct sigaction act, oldact;
act.sa_handler = catchSignal;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
int ret = sigaction(SIGALRM, &act, &oldact);
if(ret == -1){
perror("sigaction");
exit(1);
}
alarm(seconds);
ret = pause();
if(ret == -1 && errno == EINTR){
printf("signal caught!\n");
}
ret = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
return ret;
}
int main(){
while(1)mySleep(1);
}
|
时序竞态
一个会出问题的例子

若CPU失去时间太长,pause将永远不会被唤醒。
sigsuspend 函数

相当于带有信号屏蔽字的pause函数,可以解决以上问题。(原子操作)

流程:
- 注册SIGALRM处理函数
- 阻塞SIGALRM
- alarm→sigsuspend【解除阻塞SIGALRM→pause→继续阻塞SIGALRM】
- 解除SIGALRM处理函数
- 解除阻塞SIGALRM。
这样可以解决pause的时序问题。
全局变量异步IO问题
父子进程之间交替数数程序。运行一段时间后两者都永久等待。
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
|
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int n = 0, flag = 0; //定义两个全局变量(注意了)
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int num) //子进程的用户处理函数
{
printf("I am child %d\t%d\n", getpid(), n);
n += 2;
flag = 1; //对全局变量的修改
}
void do_sig_parent(int num) //父进程的用户处理函数
{
printf("I am parent %d\t%d\n", getpid(), n);
n += 2;
flag = 1; //对全局变量的修改
}
int main(void)
{
pid_t pid;
struct sigaction act;
if ((pid = fork()) < 0)
sys_err("fork");
else if (pid > 0) {
n = 1; //父进程从1开始数
sleep(1); //父进程睡眠1s确保在父进程向子进程发信号之前,子进程完成了对信号的注册
act.sa_handler = do_sig_parent;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR2, &act, NULL); //注册自己的信号捕捉函数,父进程使用SIGUSR2信号
do_sig_parent(0); //父进程先进行数数,从1开始
while(1) {
/* wait for signal */;
if (flag == 1) { //父进程数数完成
kill(pid, SIGUSR1);
flag = 0; //标志已经给子进程发送完信号
}
}
} else if (pid == 0){
n = 2; //子进程从2开始数
act.sa_handler = do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGUSR1, &act, NULL);
while(1) {
/* wait for signal */;
if (flag == 1) {
kill(getppid(), SIGUSR2);
flag = 0;
}
}
}
return 0;
}
|
问题在于flag的修改,若某一进程修改不及时,另一进程已经发送了信号,那么就会导致原进程在执行完信号后才修改flag为0,那么两者就会死锁。
这里的全局是原程序和内核回调函数的并行。
改进方法:直接不使用flag,把信号发送放到回调函数里面。
可重入函数

显然这个是不可重入的函数,主要原因是使用了全局变量。
注意事项:
- 可重入函数不能有全局变量以及static变量。不能有malloc和free。
- 信号捕捉函数应该设计为可重入函数。
- 信号处理程序可以调用的可重入函数可以参阅man 7 signal.
- 没有在以上列表中的函数大多是不可重入的。
- 静态数据结构
- 调用了malloc和free
- 是标准的IO函数
SIGCHLD回收子进程

对父进程发送信号。可以利用SIGCHLD信号回收子进程。
为了避免信号的覆盖,需要在回收函数里面使用while来处理所有的子进程。

这里使用if时,会使得一些进程没有回收。
信号传参
发送参数
sigqueue函数对应kill函数,可以在发送信号的同时携带参数。

但是注意传地址时,每个进程的虚拟地址空间独立,把虚拟地址传到另一进程没有实际意义。
接收参数

中断系统调用
系统调用:
- 慢速系统调用:可能会使得本进程永久阻塞。如pause、wait、waitpid、read
- 其他系统调用。
对于慢速系统调用,被信号打断后,按需求希望恢复操作或者跳过操作(read和pause)。

发送信号时,sa_flags参数可以设置是否被信号中断后重启。也可以设置该信号不自动被屏蔽。
终端、进程组、会话、守护进程
终端
是所有输入输出设备的总称。所有的进程都有一个父进程init。每个进程都可以通过/dev/tty访问它的控制终端。
启动流程

线路规程像一个过滤器,对某些字符进行特殊处理。如ctrl+c会被线路规程截获,而不是读到read中。

ttyname函数
借助ttyname函数可以看到不同终端对应的设备文件名。
网络终端

进程组 ps ajx
作业就是进程组,代表一个或多个进程的集合。
当父进程创建子进程,默认子进程与父进程为同一个组,组ID为父进程的ID(组长)。

getpgrp 获取进程组ID
getpgid 获取指定进程的进程组id
setpgid 设定指定进程的进程组id
注意非root进程只能改变自己的子进程。
会话
一组进程组可以编号成一个会话。
创建会话

getsid 查看会话id
setsid 设置会话id
会话可以做守护进程。
守护进程
Daemon进程,是Linux中的后台服务进程,通常独立于控制终端且周期性执行某种任务或等待某个事件。通常以d结尾(httpd、sshd)。且不受用户登录退出的影响。
最关键的一步:使用setsid创建一个新的session,并成为session leader。
创建方式
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
|
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
pid_t pid, spid;
pid = fork();
if(pid == 0) //child
{
spid = setsid();
int ret = chdir("/home/hongwei");
if(ret == -1){
perror("chdir");
exit(1);
}
umask(0002);
close(STDIN_FILENO);
int fd = open("/dev/null", O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
while(1){
sleep(1);
}
}
else if(pid > 0){
return 0;
}
return 0;
}
|
在bashrc文件中加入运行命令,则可以保证每次开机自动启动。
线程
概念
Linux下仍是轻量级的进程,差别不大。
进程:独立的地址空间、PCB
线程:也有PCB,但是没有独立的地址空间(共享)。

进程(独居),线程(合租)
线程是最小的执行单位,进程是最小的资源分配的单位。
Linux线程实现原理
- 线程是由进程发展而来,底层实现类似。都是用clone(),也有PCB。
- 从内核的角度来看,进程和线程都一样,有不同的PCB。但是PCB中指向内存的三级页表对于线程来说是相同的。



内核中的栈用于记录临时的寄存器值,便于恢复。
LWP号(线程号,区分于线程id) ps -Lf pid。
线程号:CPU分配运行时间的依据。
线程id:进程内部区分线程的方法。
线程共享资源
- 文件描述符表
- 每种信号的处理方式(尽量不要线程和信号一起使用)
- 当前工作目录
- 用户ID和组ID
- 内存地址空间 .text .data .bss heap 共享库
非共享资源
- 线程id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户栈)
- errno变量
- 信号屏蔽字
- 调度优先级
优缺点
优点:
缺点:
- 库函数不稳定(pthread)
- 调试困难,gdb不支持
- 对信号支持不好
在Linux下,由于实现方法导致进程线程的差别不大。
控制原语
pthread_self
对应getpid()获取线程id。
pthread_create
对应fork()

传出线程id,传入属性(可以是NULL),主控函数,函数参数。
注意返回值为错误编号(非-1)。
存疑:注意传参时最好为值传递,内存地址情况可能会发生改变。
pthread_exit
将单个线程退出。如果是主控线程,则可以使得子线程继续运行而非退出。exit函数会直接把进程结束。return作用为返回值到调用者处。
pthread_join
对应waitpid()。回收线程。

返回的retval若传指针则需要malloc。也可以在main中malloc。

线程的回收不需要一定是主控线程。
pthread_detach
实现线程分离,对线程在状态上实现分离,其退出状态不被获取,且自己释放,不会成为僵尸线程。

pthread_cancel
对应kill,直接杀死某线程。但是需要线程到达一定的检查点。
使用man 7 pthreads 查看所有取消点。如read pause open creat close…
也可以自定义取消点 pthread_testcancel()。
原语对比
getpid : pthread_self
fork: pthread_create
wait: pthread_join(tid, void**)
exit(): pthread_exit(void*)
- kill(): pthread_cancel(); 到达取消点
-
pthread_detach 自动清理pcb
线程属性

pthread_attr_init/destroy 函数初始化和销毁这个属性结构体。


调整栈的大小使得能建立更多的线程。

注意此时是在堆上创建线程。
NPTL 线程库版本
getconf GNU_LIBPTHREAD_VERSION
注意事项
- 避免僵尸进程 join detach
- 避免使用fork()
- 避免使用信号
线程同步
概念
线程的同步是指协同,互相配合,线程按照一定的先后次序运行。
一个线程发出某一功能调用时,直到这个功能调用结束为止,其他线程不调用这个功能。
数据发生时间错误条件:
互斥量 mutex
结构体pthread_mutex_t(可以看做一个整数)。
pthread_mutex_init(destory)


restrict关键字:
如何理解C语言关键字restrict?
restrict 是为了告诉编译器额外信息(两个指针不指向同一数据),从而生成更优化的机器码。

pthread_mutex_lock
用阻塞等待方法加锁。

pthread_mutex_trylock
不阻塞方法加锁。(轮询)
pthread_mutex_unlock
解锁。
锁的粒度应该越小越好。
死锁
1、线程尝试对锁加两次锁。
2、多个线程互相等待。

解决方法:
- 使用pthread_mutex_trylock,若失败则放弃已有的锁,日后再加锁。
读写锁
读写锁是一把锁。具备三种状态
写独占,读共享,写锁优先度高。(写者优先)pthread_rwlock_t

条件变量
条件变量不是锁,但是可以造成线程阻塞,通常与互斥锁搭配使用。 pthread_cond_t


pthread_cond_wait
pthread_cond_wait函数有三个功能:
- 阻塞等待一个条件变量满足。
- 释放已经掌握的互斥锁。
- 当被唤醒,解除阻塞并重新申请获取互斥锁。
使用pthread_cond_signal唤醒一个线程。pthead_cond_boardcast唤醒所有线程。
pthread_cond_timedwait
在指定时间内等待。

这里的abstime为timespec结构体:

abstime是绝对时间(相对于unix诞生时间):
1
2
3
4
|
time_t cur = time(NULL);
struct timespec t;
t.tv_sec = cur + 1;
pthread_cond_timedwait(&cond, &mutex, &t);
|
使用条件变量解决生产者消费者模型

条件变量代表是否缓冲区有产品。
条件变量的优点在于减少了竞争,消费者之间在生产时不再需要竞争互斥锁。
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
|
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
struct msg{
int num;
struct msg *next;
};
struct msg *head = NULL, *mp = NULL, *mc = NULL;
void* producer(void* arg){
while(1){
mp = malloc(sizeof(struct msg));
mp -> num = rand() % 400 + 1;
printf("=>produced %d\n", mp -> num);
pthread_mutex_lock(&mutex);
mp -> next = head;
head = mp;
pthread_mutex_unlock(&mutex);
int ret = pthread_cond_signal(&has_product);
if(ret != 0){
printf("signal failed\n");
pthread_exit(NULL);
}
sleep(rand() % 3);
}
return NULL;
}
void* consumer(void* arg){
while(1){
pthread_mutex_lock(&mutex);
while(head == NULL){
pthread_cond_wait(&has_product, &mutex);
}
mc = head;
head = mc -> next;
pthread_mutex_unlock(&mutex);
printf("=>consumed: %d\n", mc->num);
free(mc);
mc = NULL;
sleep(rand() % 2);
}
return NULL;
}
int main()
{
pthread_t ptid, ctid;
int ret = pthread_create(&ptid, NULL, producer, NULL);
if(ret != 0){
printf("create error\n");
exit(1);
}
ret = pthread_create(&ctid, NULL, consumer, NULL);
if(ret != 0){
printf("create error\n");
exit(1);
}
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
}
|
信号量(也可以应用于进程间同步)
进化版的互斥锁。留意信号量的函数都是用errno来返回错误信息。
sem_t 信号量结构体。
sem_init
初始化信号量。

第二个参数代表是否能在进程间共享,当非0,表示可以共享。
sem_destroy
销毁信号。
sem_wait
信号量减一
若信号量小于0,线程阻塞。

sem_trywait
sem_timedwait
sem_post
信号量加一,若信号量小于等于0,同时唤醒信号量上的进程。
生产者消费者
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
|
void *producer(void *arg)
{
int i = 0;
while (1) {
sem_wait(&blank_number); //生产者将空格子数--,为0则阻塞等待
queue[i] = rand() % 1000 + 1; //生产一个产品
printf("----Produce---%d\n", queue[i]);
sem_post(&product_number); //将产品数++
i = (i+1) % NUM; //借助下标实现环形
//sleep(rand()%3);
}
}
void *consumer(void *arg)
{
int i = 0;
while (1) {
sem_wait(&product_number); //消费者将产品数--,为0则阻塞等待
printf("-Consume---%d\n", queue[i]);
queue[i] = 0; //消费一个产品
sem_post(&blank_number); //消费掉以后,将空格子数++
i = (i+1) % NUM;
//sleep(rand()%3);
}
}
|
为什么不需要互斥锁?
进程间同步
互斥量
进程中的互斥量需要修改mutex的属性。

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
|
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/wait.h>
struct mt {
int num;
pthread_mutex_t mutex;
pthread_mutexattr_t mutexattr;
};
int main(void)
{
int i;
struct mt *mm;
pid_t pid;
/*
int fd = open("mt_test", O_CREAT | O_RDWR, 0777);
ftruncate(fd, sizeof(*mm));
mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
unlink("mt_test");
*/
mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
memset(mm, 0, sizeof(*mm));
pthread_mutexattr_init(&mm->mutexattr); //初始化mutex属性对象
pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED); //修改属性为进程间共享
pthread_mutex_init(&mm->mutex, &mm->mutexattr); //初始化一把mutex琐
pid = fork();
if (pid == 0) {
for (i = 0; i < 10; i++) {
pthread_mutex_lock(&mm->mutex);
(mm->num)++;
pthread_mutex_unlock(&mm->mutex);
printf("-child----------num++ %d\n", mm->num);
}
} else if (pid > 0) {
for ( i = 0; i < 10; i++) {
// sleep(1);
pthread_mutex_lock(&mm->mutex);
mm->num += 2;
pthread_mutex_unlock(&mm->mutex);
printf("-------parent---num+=2 %d\n", mm->num);
}
wait(NULL);
}
pthread_mutexattr_destroy(&mm->mutexattr); //销毁mutex属性对象
pthread_mutex_destroy(&mm->mutex); //销毁mutex
munmap(mm,sizeof(*mm)); //释放映射区
return 0;
}
|
文件锁
fcntl函数
修改已经打开文件的属性,修改阻塞和非阻塞。

可以借助其来设置文件锁。

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
|
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int fd;
struct flock f_lock;
if (argc < 2) {
printf("./a.out filename\n");
exit(1);
}
if ((fd = open(argv[1], O_RDWR)) < 0)
sys_err("open");
f_lock.l_type = F_WRLCK; /*选用写琐*/
// f_lock.l_type = F_RDLCK; /*选用读琐*/
f_lock.l_whence = SEEK_SET;
f_lock.l_start = 0;
f_lock.l_len = 0; /* 0表示整个文件加锁 */
fcntl(fd, F_SETLKW, &f_lock);
printf("get flock\n");
sleep(10);
f_lock.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &f_lock);
printf("un flock\n");
close(fd);
return 0;
}
|
多线程间不可以用文件锁,因为文件描述符共享。