金库->银行->办事窗口(客户)
系统编程就是利用系统调用提供的这些接口、或者说函数、去操作磁盘、终端、网络等硬件。
系统调用:system call类比 银行的办事窗口
问你原理性的东西,不会问你那个参数是干什么的。
《 Linux:系统编程》的前置知识有《 Linux操作系统基础》、《C语言程序设计》、《数据结构》
本课程将带你一步一步学会在 Linux操作系统下编程,使用系统底层提供给我们的各种接口和函数,井深入内核,体验系统底层的精妙之处。
《 Linux E网络编程基础》的前置课程是《 Linux:系统编程》,在本课程中,我们需要重点学习计算机网络知识,特别是运输层的TCP与UDP协议,网络层的路由协议与IP协议。在学习了基础的计算机网络知识后,我们会从 socket入手,学习基于TCP和UDP的多种网络通讯模型
学完本节课程后,同学将掌握文件的打开、关闭、读写,阻塞与非阻塞IO,同步1IO,文件系统,标准IO,流的打开、关闭与读写,控制缓冲,线程安全:对文件加锁等内容
1.菜鸟驿站(带缓冲区的)
2.一切皆文件,需要实时操作的内容最好直接使用系统调用
全缓冲
行缓冲: stdout是行缓冲
无缓冲:stderr是无缓冲
#include
int main() {
int i;
for (int i = 0; i < 1025; i++) {
fputc('A', stdout);
//linux的缓冲区大小可能是1024个,
//因为这里只有在1025的时候,才会有缓冲区刷新
}
while(1){;}
return 0;
}
#include
ssize_t write(int fd, const void *buf, size_t count);
#include
#include
#include
#include
#include
#include
int main (int argc, char ** argv) {
if (argc < 2) {
printf("Usage : cmd + filename\n");
exit(1);
}
FILE *fp = fopen(argv[1], "r");
char c;
while ( (c = fgetc(fp))!= EOF) {
printf("%c", c);
}
fclose(fp);
return 0;
#include
#include
#include
#include
#include
int main (int argc, char ** argv) {
if (argc < 3){
printf("Usage : cmd + srcFilename + dstFilename\n");
exit(1);
}
FILE *fp1 = fopen(argv[1], "r+");
if (!fp1) {
perror("open srcFile");
exit(1);
}
FILE *fp2 = fopen(argv[2], "w");
if (!fp2) {
perror("open dstFile");
exit(1);
}
char c;
while ( (c = fgetc(fp1))!= EOF) {
printf("%c", c);
fputc(c, fp2);
}
fclose(fp2);
fclose(fp1);
return 0;
}
NAME
fgetpos, fseek, fsetpos, ftell, rewind - reposition a stream
SYNOPSIS
#include
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
DESCRIPTION
The fseek() function sets the file position indicator for the stream pointed to by
stream. The new position, measured in bytes, is obtained by adding offset bytes to
the position specified by whence. If whence is set to SEEK_SET, SEEK_CUR, or
SEEK_END, the offset is relative to the start of the file, the current position in‐
dicator, or end-of-file, respectively. A successful call to the fseek() function
clears the end-of-file indicator for the stream and undoes any effects of the
ungetc(3) function on the same stream.
#include
int main(int argc, char **argv) {
FILE *fp = fopen(argv[1], "r");
if (!fp) {
perror("open file");
return 1;
}
fseek(fp, 0, SEEK_END);
printf("Size = %ld", ftell(fp));
fclose(fp);
return 0;
}
重定向流
#include
#include
#include
#include
#include
#include
#include
#include
int main(){
struct winsize size;
if (!isatty(1)) {
perror("1 is not tty\n");
exit(1);
}
if (ioctl(1, TIOCGWINSZ,&size) < 0) {
perror("ioctl");
exit(1);
}
printf("%d rows, %d colums\n", size.ws_row, size.ws_col);
return 0;
}
文件系统中存储的最小单位是块(Block, ー个块究竟多大是在格式化时确定的,例如mke2fs的-b选项可以设定块大小为1024、2048或4096字节。
大小就是1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能使用该块
The stat structure
All of these system calls return a stat structure, which contains the following fields:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
#include
#include
#include
#include
#include
#include
int main(int argc, char** argv){
if (argc < 2) {
printf("usage : cmd + filename/dirname\n");
exit(1);
}
struct stat st;
/**
*这两行代码互换,有什么区别?
*stat和lstat有什么区别?
*/
//stat(argv[1], &st);
lstat(argv[1], &st);
/*
if (S_ISDIR(st.st_mode)) {
printf("directory\n");
} else {
printf("other file type\n");
}
*/
/**
*使用stat族函数,可以获取文件的详细信息,
*进一步得到自己想要的操作
*此处就是通过stat解析之后,判断文件的类型
*/
switch(st.st_mode & S_IFMT) {
case S_IFREG:
printf("regular file\n");
break;
case S_IFDIR:
printf("directory\n");
break;
case S_IFCHR:
printf("charactor device\n");
break;
default:
printf("other file type\n");
}
return 0;
}
Linux支持各种各样的文件系统格式,然而这些文件系统都可以 mount到某个目录下,使我们看到一个统一的目录树,各种文件系统上的目录和文件我们用ls命令看起来是一样的,读写操作用起来也都是一样的,这是怎么做到的呢? Linux内核在各种不同的文件系统格式之上做了一个抽象层,使得文件、目录、读写访问等概念成为抽象层的概念,因此各种文件系统看起来用起来都一样,这个抽象层称为虚拟文件系统(VFS, Virtualfilesystem)
ls -al
task struct结构体:ps aux
非负整数
。Resource Limit
)。pstree
fork
的作用是根据一个现有的进程复制出一个新进程,原来的进程称为父进程( ParentProcess),新进程称为子进程( Child Process)。系统中同时运行着很多进程,这些进程都是从最初只有一个进程开始一个ー个复制出来的pid_t fork(void);
/**forkOpt.c
*注意思考fork的作用
*/
#include
#include
#include
#include
int main() {
char *message;
int n;
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
//sprintf(message,"This is the child, pid = %d\n", pid);
message = "child processs";
n = 6;
} else {
//sprintf(message,"This is the parent, pid = %d\n", pid);
message = "parent processs";
n = 3;
}
for (;n > 0; n--) {
printf("%s, n = %d\n", message, n);
sleep(1);
}
return 0;
}
process % ./a.out [0]
parent processs, n = 3
child processs, n = 6
parent processs, n = 2
child processs, n = 5
parent processs, n = 1
child processs, n = 4
child processs, n = 3
dhj@DESKTOP-NA5RGM7 process % child processs, n = 2 [0]
child processs, n = 1
#include
#include
#include
#include
int main() {
char *message;
int n;
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
n = 6;
for (;n > 0; n--) {
printf("\033[31;47mc_pid self\033[0m = %d, parent pid = %d\n", getpid(), getppid());
sleep(1);
}
} else {
n = 3;
for (;n > 0; n--) {
printf("p_pid self = %d, parent pid = %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
创建10个子进程,并打印他们的pid和ppid
#include
#include
#include
#include
int main() {
int i;
for (i = 0; i < 100; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
printf("\033[31;47mchild[%d]\033[0m, self = %d, parent = %d\n", i, getpid(), getppid());
sleep(1);
break;
}
}
return 0;
}
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1
,所以exec函数只有出错的返回值而没有成功的返回值
带有字母l
(表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,最后一个可变参数应该是NULL,起 sentinel的作用。
对于带有字母v
(表示 vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,像main函数的argv参数或者环境变量表一样。
不带字母p
(表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out"。
对于带字母p
的函数:如果参数中包含/,则将其视为路径名。否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
对于以e
(表示 environment)结尾的exec函数,可以把一份新的环境变量表传给其他exec函数仍使用当前的环境变量表执行新程序
#include
#include
#include
int main() {
execlp("ls", "", "-a", "-l", NULL);//第二个参数没有起作用,此处留空了
perror("exex");
exit(1);
return 0;
}
#include
#include
#include
#include
#include
#include
int main(int argc,char **argv) {
if(argc != 3) {
printf("Usage:cmd + inputfile + outputfile\n");
exit(1);
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
perror("open inputfile");
exit(1);
}
dup2(fd, 0);//标准输入 重定向 到inputfile
close(fd);
fd = open(argv[2], O_WRONLY | O_CREAT, 0644);
if (fd < 0) {
perror("open outputfile");
exit(1);
}
dup2(fd, 1);//标准输出 重定向 到outputfile
close(fd);
//execl("/bin/ls", "/bin/ls", "-a", "-l", NULL);
execl("./upper", "./upper", NULL);//调用下面编译生成的upper可执行文件
perror("exec");
exit(0);
}
upper
可执行文件,在上个程序中调用#include
#include
int main() {
int ch;
while((ch = getchar()) != EOF) {
putchar(toupper(ch));
}
return 0;
}
#include
/*
*循环打印环境变量
*/
int main(void) {
extern char **environ;
for(int i = 0;environ[i];i++){
printf("%s\n",environ[i]);
}
return 0;
}
#include
#include
#include
int main(void) {
/*
extern char **environ;
for(int i = 0;environ[i];i++){
printf("%s\n",environ[i]);
}
*/
printf("path value = [%s]\n", getenv("PATH"));
setenv("PATH", "hell", 1);
printf("path value = [%s]\n", getenv("PATH"));
return 0;
}
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
例如:一个进程的退出状态可以在SheI中用特殊变量$?查看,因为 She l I是它的父进程,当它终止时SheI调用wait或 waitpid得到它的退出状态同时彻底清除掉这个进程。
ps -u
查看僵尸进程ctrl+c
强制停止,你会发现,这两个都被收尸了;a.out
是被调用a.out的bash先收尸的,然后子进程就变成了孤儿僵尸,被1号进程
(孤儿院
)收尸了。#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
while (1)
sleep(1);
} else {
exit(3);
}
return 0;
}
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) {
int n = 5;
while (n > 0) {
printf("this is child process\n");
sleep(1);
n--;
}
exit(4);
} else {
int stat_val;
waitpid(pid, &stat_val, 0);
if (WIFEXITED(stat_val)) {
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
} else if(WIFSIGNALED(stat_val)) {
printf("CHild terminated abnormally, signal %d\n", WEXITSTATUS(stat_val));
}
}
exit(0);
}
#include
#include
#include
#include
#include
int main () {
pid_t pid;
int fd[2];
int n;
char buf[20];
if(pipe(fd) < 0) {
perror("pipe");
exit(1);
}
/**
*前面先创建一个管道,后面fork
*父进程往管道里面写,子进程从管道里读
*/
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid > 0) {
close(fd[0]);
write(fd[1], "hello pipe\n", 11);
wait(NULL);
} else {
close(fd[1]);
sleep(1);
n = read(fd[0], buf, 20);
write(1, buf, n);
}
return 0;
}
如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
如果有指向管道写端的文件描述符没关闭,而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端 write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞, 直到管道中有空位置了才写入数据并返回。
函数 popen
先执行fork,然后调用exec以执行 command,并且返回一个标准I/O文件指针。
如果type是"r",则文件指针连接到cmd的标准输出。
如果type是"w",则文件指针连接到cmd的标准输入。
函数pclose
关闭标准I/O流,等待命令执行结束,然后返回cmd的终止状态。如果cmd不能被执行,则 pclose返回的终止状态与 shell执行exit一样。
#include
#include
#include
int main() {
FILE *fp = popen("./upper", "w");
if (!fp) {
perror("popen");
exit(1);
}
/*用popen打开的,fp占据着标准输出
* 此fp的内容处理完会输出到终端
*/
fprintf(fp, "hello world 3 \n ttt survive thrive\n");
pclose(fp);
return 0;
}
#include
#include
#include
int main() {
FILE *fp = popen("cat ./out.txt", "r");
if (!fp) {
perror("popen");
exit(1);
}
int c;
while(~(c = fgetc(fp)))
putchar(toupper(c));
pclose(fp);
return 0;
}
#include
#include
#include
#include
int main() {
key_t key = ftok("./callback.c", 9);
if (key < 0) {
perror("ftok");
exit(1);
}
printf("key = ox%x\n", key);
//创建共享内存,此处IPC_EXCL表示必须自己创建
int shmid = shmget(key, 20, IPC_CREAT /*| IPC_EXCL*/ | 0666);
if (shmid < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
return 0;
}
#include
#include
#include
#include
#include
int main() {
key_t key = ftok("./callback.c", 9);
if (key < 0) {
perror("ftok");
exit(1);
}
printf("key = ox%x\n", key);
int shmid = shmget(key, 20, IPC_CREAT /*| IPC_EXCL*/ | 0666);
if (shmid < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
char *shmp = shmat(shmid, NULL, 0);
if (shmp < 0) {
perror("shmat");
exit(1);
}
printf("shmp = %p\n", shmp);
//往共享内存中写数据
//snprintf(shmp, 20, "hello\n");
printf("%s", shmp);
shmdt(shmp);//取消内存映射关系
//如果再此处访问共享内存会怎样?
//printf("%s", shmp);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
key_t key = ftok(".", 10);
if (key < 0) {
perror("ftok");
exit(1);
}
printf("key = ox%x\n", key);
int shmid = shmget(key, 20, IPC_CREAT /*| IPC_EXCL*/ | 0666);
//也可以指定内存key,如下:
//int shmid = shmget((key_t)123456, 20, IPC_CREAT /*| IPC_EXCL*/ | 0666);
if (shmid < 0) {
perror("shmget");
exit(1);
}
printf("shmid = %d\n", shmid);
char *shmp = shmat(shmid, NULL, 0);
if (shmp < 0) {
perror("shmat");
exit(1);
}
printf("shmp = %p\n", shmp);
//往共享内存中写数据
/*
int i;
char *p = shmp;
for(i = 0; i < 2048; i++) {
p[i] = 'a';
}
p[i] = 'a';
*/
//用来清空共享内存
bzero(shmp, 20);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
//parent process
while(1) {
scanf("%s",shmp);
if (!strcmp(shmp, "quit"))
break;
}
wait(NULL);
} else {
//child process
while (1) {
if(!strcmp(shmp, "quit"))
break;
if(*shmp)
printf("%s\n", shmp);
bzero(shmp, 20);
sleep(1);
}
}
printf("%s\n", shmp);
shmdt(shmp);//取消内存映射关系
//如果再此处访问共享内存会怎样?
//printf("%s", shmp);
return 0;
}
ipcs -m shmid
释放共享内存ipcs [0]
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 4 ubuntu 666 1024 0
0x0a050002 5 ubuntu 666 20 0
0x0a050001 6 ubuntu 666 20 0
0x0001e240 7 ubuntu 666 20 2
~ % ipcrm -m 4
~ % ipcrm -m 5
~ % ipcrm -m 6
~ % ipcrm -m 7
~ % ipcs [0]
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
Ctrl-C
,这个键盘输入产生一个硬件中断。kill -l
命令可以察看系统定义的信号列表Term
表示终止当前进程,Core
表示终止当前进程并且Core Dump
,Ign
表示忽略该信号,Stop
表示停止当前进程,Cont
表示继续执行先前停止的进程操作系统能够进行运算调度的最小单位
。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。由于同一进程的多个线程共享同一地址空间,因此 Text Segment、 Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
但有些资源是每个线程各有一份的
-lpthread
选项回调函数
是为了给后人开门。在早期的程序设计的时候,不知道后来人需要实现什么功能,这一部分就让后来使用的人自己实现,PTHREAD_CREATE(3) Linux Programmer's Manual PTHREAD_CREATE(3)
NAME
pthread_create - create a new thread
SYNOPSIS
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
#include
#include
#include
#include
#include
#include
void printid(char *);
void *thr_fn(void *arg) {
//todo
printid(arg);
return NULL;
}
void printid(char *tip) {
pid_t pid = getpid();
pthread_t tid = pthread_self();
printf("%s pid: %u tid:%u (%p)\n", tip, pid, tid, tid);
// printf("%s thr_fn=%p\n", tip, thr_fn);
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_fn, "new thread");
if (ret) {
printf("create thread err:%s\n", strerror(ret));
exit(1);
}
sleep(1);
printid("main thread");
return 0;
}
pthread_self
而是直接打印这个ntid,能不能达到同样的效果? The new thread terminates in one of the following ways:
* It calls pthread_exit(3), specifying an exit status value
that is available to another thread in the same process
that calls pthread_join(3).
* It returns from start_routine(). This is equivalent to
calling pthread_exit(3) with the value supplied in the
return statement.
* It is canceled (see pthread_cancel(3)).
* Any of the threads in the process calls exit(3), or the
main thread performs a return from main(). This causes
the termination of all threads in the process.
#include
#include
#include
#include
#include
#include
void *thr_fn1(void *arg) {
printf("thread 1 returning\n");
return (void *) 1;
}
void *thr_fn2(void *arg) {
printf("thread 2 exiting\n");
pthread_exit((void *)2);
return NULL;
}
void *thr_fn3(void *arg) {
while(1) {
printf("thread 3 sleeping\n");
sleep(1);
}
return (void *) 1;
}
int main() {
pthread_t tid;
void *sts;
pthread_create(&tid, NULL, thr_fn1, NULL);
pthread_join(tid, &sts);
printf("thread 1 exit code %ld\n", (long)sts);
pthread_create(&tid, NULL, thr_fn2, NULL);
pthread_join(tid, &sts);
printf("thread 2 exit code %ld\n", (long)sts);
pthread_create(&tid, NULL, thr_fn3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &sts);
printf("thread 3 exit code %ld\n", (long)sts);
return 0;
}
thread 1 returning
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2
thread 3 sleeping
thread 3 sleeping
thread 3 sleeping
thread 3 exit code -1
线程(thread)是允许应用程序并发的执行多个任务的一种机制。一个进程可以有多个线程,如果每个线程执行不同的任务,通过对线程的执行顺序进行控制(调度)就可以实现任务的并发执行。当然了多进程也可以实现任务的并发处理,但是两者之间是有区别的。最大的区别就是拥有的资源不同。进程拥有自己的独立系统资源,而线程没有独立资源,只能和属于同一进程的其他线程共享进程的系统资源。单个资源在多个用户之间共享就会存在一致性的问题,因此需要通过一定的方法来对线程共享资源进行同步。
目前线程间同步主要有互斥量、读写锁、条件变量、自旋锁、屏障等5种方式。
互斥量(mutex)
:主要用于保护共享数据,确保同一时间只有一个线程访问数据。互斥量从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,访问完成后释放互斥量(解锁)。对互斥量进行加锁之后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。这样就可以保证每次只有一个线程可以向前执行。
读写锁(reader-writer lock)
:读写锁也叫做共享互斥锁(shared-exclusive lock),它有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只能有一个线程可以占有写模式的读写锁,但是多个线程可以同时战友读模式的读写锁。因此与互斥量相比,读写锁允许更高的并行性。读写锁非常适合对数据结构读的次数远大于写的情况。
条件变量
:是线程可用的另一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因此互斥量必须在锁住以后才能计算条件。
自旋锁
:自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取所之前一直处于忙等(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。自旋锁用在非抢占式内核中时是非常有用的,除了提供互斥机制以外,还可以阻塞中断,这样中断处理程序就不会陷入死锁状态。
屏障(barrier)
:是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。
多个线程同时访问共享数据时可能会冲突,这跟前面讲信号时所说的可重入性是同样的问题。比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:
#include
#include
#include
#include
#include
#include
int cnt = 0;
void *cntadd(void *arg) {
int val, i;
for(i = 0; i < 10; i++){
val = cnt;
printf("%p : val = %d cnt = %d\n", pthread_self(),val, cnt);
cnt = val + 1;
}
return NULL;
}
int main(){
pthread_t tida, tidb;
pthread_create(&tida, NULL, cntadd, NULL);
pthread_create(&tidb, NULL, cntadd, NULL);
pthread_join(tida,NULL);
pthread_join(tidb,NULL);
return 0;
}
#include
#include
#include
#include
#include
#include
pthread_mutex_t add_lock = PTHREAD_MUTEX_INITIALIZER;
int cnt;
void *cntadd(void *arg) {
int val, i;
for(i = 0; i < 10; i++){
pthread_mutex_lock(&add_lock);
val = cnt;
printf("%p : val = %d cnt = %d\n", pthread_self(),val, cnt);
cnt = val + 1;
pthread_mutex_unlock(&add_lock);
}
return NULL;
}
int main(){
pthread_t tida, tidb;
pthread_create(&tida, NULL, cntadd, NULL);
pthread_create(&tidb, NULL, cntadd, NULL);
pthread_join(tida,NULL);
pthread_join(tidb,NULL);
return 0;
}
压栈
,消费者如出栈
,类似后进先出#include
#include
#include
#include
#include
typedef struct Goods {
int data;
struct Goods *next;
} Goods;
Goods *head = NULL;
pthread_mutex_t headlock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t hasGoods = PTHREAD_COND_INITIALIZER;
void *producer(void *arg) {
Goods *ng;
while (1) {
ng = (Goods *)malloc(sizeof(Goods));
ng->data = rand() % 100;
pthread_mutex_lock(&headlock);
ng->next = head;
head = ng;
pthread_mutex_unlock(&headlock);
pthread_cond_signal(&hasGoods);
printf("produce %d\n", ng->data);
sleep(rand() % 2);
}
}
void *consumer(void *arg) {
Goods *k;
while(1) {
pthread_mutex_lock(&headlock);
if(!head) {
pthread_cond_wait(&hasGoods, &headlock);
}
k = head;
head = head->next;
pthread_mutex_unlock(&headlock);
printf("\033[31;47mconsume\033[0m %d\n", k->data);
free(k);
sleep(rand() % 4);
}
}
int main() {
srand(time(NULL));
pthread_t pid,cid;
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
#include
#include
#include
#include
#include
#include
#define NUM 5
int q[NUM];
sem_t blank_number, goods_number;
void *producer(void *arg) {
int i = 0;
while (1) {
sem_wait(&blank_number);
q[i] = rand() % 100 + 1;
printf("produce %d\n", q[i]);
sem_post(&goods_number);
i = (i + 1) % NUM;
sleep(rand() % 1);
}
}
void *consumer(void *arg) {
int i = 0;
while(1) {
sem_wait(&goods_number);
printf("\033[31;47mconsume\033[0m %d\n", q[i]);
q[i] = 0;
sem_post(&blank_number);
i = (i + 1) % NUM;
sleep(rand() % 4);
}
}
int main() {
srand(time(NULL));
pthread_t pid,cid;
sem_init(&blank_number, 0, NUM);
sem_init(&goods_number, 0, 0);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}