杨记

碎片化学习令人焦虑,系统化学习使人进步

0%

Linux编程实验

学自电子科技大学

实验一 开发环境实验

GCC

GNU Compiler Collection

包括:

  • cpp(预处理器)
  • gcc(c 编译器)、g++(c++编译器)等编译器
  • binutils等二进制工具,含【as(汇编器)、 ld(链接器)等等】

image-20220411132146964

gcc [调试选项] <文件名>

  • -g:以操作系统的本地格式(stabs, COFF, XCOFF,或DWARF)。产生调试信息. GDB能够使用这些调试信息.
  • -ggdb:ggdb选项可以插入的更为丰富的调试信息,但是生成的可执行文件有可能无法用其它调试器调试

注:调试选项的启动会让二进制文件的大小急剧增长

gcc [优化选项] <文件名>

  • -O 或者 –O1 优化选项:编译器会试图减少目标码的大小和执行时间
  • -O2:除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作,但不进行循环展开和函数内嵌
  • -O3:乱序执行,循环展开的优化

注:调试时不能用优化选项,否则变量值和源代码无法对应

GDB

GDB是GNU计划开发的程序调试工具
包括:

  • 启动程序,可以按照自定义的要求运行程序
  • 指定断点停住(断点可以是条件表达式)
  • 程序停住时,可以检查当前程序中的情况
  • 动态的改变程序的执行环境

watchpoint命令(观察对象值变化,则程序立即停止):

语句格式 功能
watch <expr> 为表达式(变量)expr设置一个观察点。一旦表达式值有变化时,马上停住程序
rwatch <expr> 当表达式(变量)expr被读时,停住程序
awatch <expr> 当表达式(变量)的值被读或被写时,停住程序
info watchpoints 列出当前所设置了的所有观察点

清除禁止断点或者观察点:

语句格式 功能
clear [linenum] [function name] 清除所有断点,不会清除watchpoints
delete <num> 清除编号为num的断点或者watchpoint
disable <num> 禁止某个断点
enable <num> 开启某个断点

其他常用命令:

语句格式 功能
Info [**] 查看设置的断点,函数名称,类名,例:info b查看已经设置的断点名称
dir 源代码路径 gdb默认有$cdir和$cwd两个源代码搜索路径,如果你要调试程序的源代码不在这两个路径中,可用dir命令增加
r [[参数1]…[参数n]] 在gdb中运行已经装载的可执行文件,参数为程序所需的参数

list命令(简写l,调试过程中打印源码):

语句格式 功能
list 当前行的上5行和下5行,默认10行
list <linenum> 打印Linenum行的代码
`list <+offset>\ list <-offset>` 当前行号的正偏移量\ 当前行号的负偏移量
list <filename:linenum> Filename文件的linenum行
list <filename:function> Filename文件的function函数
list <*address> 运行时语句在内存中的地址

搜索代码命令:

语句格式 功能
forward-search <regexp> 向前搜索
search <regexp> 向后搜索
reverse-search <regexp> 全文搜索

注:<regexp>:正则表达式,匹配字符串的模式

GDB调试命令:

调试命令 功能
step 单步调试命令,一次执行一行程序
next 单步调试命令,但跳过函数调用
finish 单步调试时直接从一个函数中返回
disassemble 显示汇编代码
Backtrace或者bt 查看目前程序的堆栈情况
whre 查看目前程序的堆栈情况
up/down 向上或者向下移动一个堆栈
frame<num>或者f 移动到第num个堆栈

注:当移动到某个堆栈时,可以用gdb命令查看在此堆栈中的局部变量。

代码

在Linux下通过C语言程序设计链表应用程序

  • 定义单向链表的数据结构
  • 创建链表
  • 插入结点
  • 遍历结点等
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
96
97
98
99
100
101
102
103
104
105
106
107
//list.c
#include <stdio.h>
#include <stdlib.h>

//预定义数据结构
typedef struct stuInfo
{
char stuName[10]; //学生姓名
int Age; //年龄
}ElemType;

typedef struct node
{
ElemType data;
struct node *next;
}ListNode, *ListPtr;

ListNode* CreateList()
{
static ListNode *head;
head = (ListNode*)malloc(sizeof(ListNode));
return head;
}

void PrintList(ListNode *head)
{
if(head == NULL)
{
printf("error! please create list first\n");
return;
}
ListNode *tail = head->next;
printf("姓名\t年龄\n");
while(tail != NULL)
{
printf("%s\t%d\n", tail->data.stuName, tail->data.Age);
tail = tail->next;
}
}

void InsertList(ListNode* head)
{
if(head == NULL)
{
printf("error! please create list first\n");
return;
}
ListNode *tail = head;
while(tail->next != NULL) tail = tail->next;
tail->next = (ListNode*)malloc(sizeof(ListNode));
printf("input name: ");
scanf("%s", tail->next->data.stuName);
printf("input age: ");
scanf("%d", &(tail->next->data.Age));
}

void deleteList(ListNode* head)
{
while(head != NULL)
{
ListNode* tmp = head;
head = head->next;
free(tmp);
}
}

int main(int argc, char *argv[])
{
ListNode *ListHead = NULL;
while(1)
{
printf("1 Create List\n");
printf("2 Printf List\n");
printf("3 Insert List\n");
printf("4 Quit\n");
char command = getchar();
switch(command)
{
case '1':
{
ListHead = CreateList();
break;
}
case '2':
{
PrintList(ListHead);
break;
}
case '3':
{
InsertList(ListHead);
break;
}
case '4':
{
deleteList(ListHead);
return 0;
}
default:
{
printf("input error\n");
}
}
while(getchar() != '\n'); // 清楚键盘缓冲区
}
return 0;
}

image-20220411144433433

实验二 文件I/O实验

内容 CP

目的一:掌握Linux应用程序命令行参数传递方法

目的二:掌握POSIX API中文件I/O操作方法

仿写cp命令的部分功能(编写mycp程序):

  • 1、将源文件复制到另外一个文件(将test1.text复制成test2.txt)
    [test@linux test]$ ./mycp /home/test1.txt /usr/test2.txt
  • 2、将源文件复制到另外一个目录(将test1.txt复制到/tmp目录)
    [test@linux test]$ ./mycp /home/test1.txt /tmp
    源文件路径和目标文件路径通过命令行参数来指定

image-20220411144742688

代码

代码:mycp.c

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
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>

#define READ_SIZE 1024

int main(int argc, char **argv)
{
if(argc < 2)
{
printf("error\n");
exit(-1);
}
char *sourcePath = argv[1];
//printf("%s %s\n", argv[1], argv[2]);
int sfd = open(sourcePath, O_RDONLY);
if(sfd == -1)
{
printf("%s:", sourcePath);
printf("%s\n", strerror(errno));
exit(-1);
}

char *targetPath = argv[2];
int tfd = -1;
if(opendir(targetPath) != NULL)
{
char *ptr = NULL;
char *fname = NULL;
ptr = strrchr(sourcePath, '/');
if(!ptr)
{
ptr = sourcePath;
}
else
{
fname = (char*)malloc(strlen(ptr));
memcpy(fname, ptr + 1, strlen(ptr+1));
ptr = fname;
}

int len = strlen(targetPath);
if(targetPath[len - 1] != '/')
{
targetPath = strcat(targetPath, "/");
}
targetPath = strcat(targetPath, ptr);
free(fname);
ptr = NULL;
fname = NULL;
//tfd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0766);
}
tfd = open(targetPath, O_RDWR | O_CREAT | O_EXCL, 0766);
if(tfd == -1)
{
printf("%s:%s\n", targetPath, strerror(errno));
exit(-1);
}

char buf[1024];
int size = -1;
do{
size = read(sfd, buf, READ_SIZE);
if(size == -1)
{
printf("%s\n", strerror(errno));
exit(-1);
}
write(tfd, buf, size);
} while(size == READ_SIZE);
printf("%s --> %s\n", sourcePath, targetPath);
close(sfd);
close(tfd);
return 0;
}

image-20220411163412553

还有一个套娃操作(^-^)

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

int main(int argc, char **argv)
{
execl("/bin/cp", argv[1], argv[2], NULL); //调用/bin/cp可执行文件
return 0;
}

实验三 文件与目录操作

内容 ls -l

目的一:掌握Linux目录操作方法

  • 打开目录、关闭目录
  • 读取目录文件
  • 获取Linux文件属性的函数

目的二:掌握Linux文件属性获取方法

目的三:掌握文件属性解析相关的重要数据结构、函数、宏和文件掩码等

具体内容:仿写ls -l的功能(编写myls程序),参数通过命令行传入:

1、获取当前工作目录路径并对该目录实现遍历

2、仿ls -l以列表形式出当前工作目录下的所有文件(包括子目录)

需显示的文件属性有:

文件类型 权限 硬链接数 所有者用户名 所有者所在组用户名 文件大小 最后修改时间

image-20220411165916173

代码

代码:myls.c

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#define _GNU_SOURCE
#include <sys/types.h>
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <grp.h>
#include <pwd.h>

void print_type(mode_t st_mode);

void print_perm(mode_t st_mode);

void print_link(nlink_t st_nlink);

void print_usrname(uid_t st_uid);

void print_grname(gid_t st_gid);

void print_size(off_t size);

void print_time(time_t mtime);

void print_filename(char *fname);

int main(int argc, char **argv)
{
char *dirPath = NULL;
if (argc == 1)
{
dirPath = get_current_dir_name();
}
else
{
dirPath = argv[1];
chdir(dirPath);
}

DIR *dir = NULL;
struct stat *currentstat;
currentstat = (struct stat *)malloc(sizeof(struct stat));
if ((dir = opendir(dirPath)))
{
struct dirent *dp = NULL;

while ((dp = readdir(dir)))
{
// printf("%s\n", dirent->d_name);
if (stat(dp->d_name, currentstat) == -1)
{
printf("get stat error\n");
continue;
}
print_type(currentstat->st_mode);
print_perm(currentstat->st_mode);
print_link(currentstat->st_nlink);
print_usrname(currentstat->st_uid);
print_grname(currentstat->st_gid);
print_size(currentstat->st_size);
print_time(currentstat->st_mtime);
print_filename(dp->d_name);
}
closedir(dir);
}
else if (stat(dirPath, currentstat) == 0)
{
print_type(currentstat->st_mode);
print_perm(currentstat->st_mode);
print_link(currentstat->st_nlink);
print_usrname(currentstat->st_uid);
print_grname(currentstat->st_gid);
print_size(currentstat->st_size);
print_time(currentstat->st_mtime);
print_filename(dirPath);
}
else
{
printf("%s:Not a directory or file\n", dirPath);
}
free(currentstat);
return 0;
}

void print_type(mode_t st_mode)
{
if (S_ISREG(st_mode))
{
printf("-");
}
else if (S_ISDIR(st_mode))
{
printf("d");
}
else if (S_ISCHR(st_mode))
{
printf("c");
}
else if (S_ISBLK(st_mode))
{
printf("b");
}
else if (S_ISFIFO(st_mode))
{
printf("p");
}
else if (S_ISLNK(st_mode))
{
printf("l");
}
else if (S_ISSOCK(st_mode))
{
printf("s");
}
}

void print_perm(mode_t st_mode)
{
//文件所有者
if ((st_mode & S_IRUSR) == S_IRUSR)
{
printf("r");
}
else
{
printf("-");
}
if ((st_mode & S_IWUSR) == S_IWUSR)
{
printf("w");
}
else
{
printf("-");
}
if ((st_mode & S_IXUSR) == S_IXUSR)
{
printf("x");
}
else
{
printf("-");
}
//同组用户
if ((st_mode & S_IRGRP) == S_IRGRP)
{
printf("r");
}
else
{
printf("-");
}
if ((st_mode & S_IWGRP) == S_IWGRP)
{
printf("w");
}
else
{
printf("-");
}
if ((st_mode & S_IXGRP) == S_IXGRP)
{
printf("x");
}
else
{
printf("-");
}
//其他用户
if ((st_mode & S_IROTH) == S_IROTH)
{
printf("r");
}
else
{
printf("-");
}
if ((st_mode & S_IWOTH) == S_IWOTH)
{
printf("w");
}
else
{
printf("-");
}
if ((st_mode & S_IXOTH) == S_IXOTH)
{
printf("x");
}
else
{
printf("-");
}

printf("\t");
}

void print_link(nlink_t st_nlink)
{
printf("%ld\t", st_nlink);
}

void print_usrname(uid_t st_uid)
{
struct passwd *usr = getpwuid(st_uid);
printf("%s\t", usr->pw_name);
}

void print_grname(gid_t st_gid)
{
struct group *grp = getgrgid(st_gid);
printf("%s\t", grp->gr_name);
}

void print_size(off_t size)
{
printf("%ld\t", size);
}

void print_time(time_t mtime)
{
// char *time = ctime(&mtime); //返回的字符串 如: Sun Apr 10 16:34:37 2022\n
// printf("%s", time);

struct tm *time = localtime(&mtime);
char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov"};
printf("%s %2d %02d:%02d\t", months[time->tm_mon], time->tm_mday, time->tm_hour, time->tm_min);
}

void print_filename(char* fname)
{
printf("%s\n", fname);
}

image-20220412124611665

实验四 进程控制实验

内容 多进程CP

目的一:掌握Linux系统创建进程的方法

目的二:掌握在代码中如何区别父子进程的方法

目的三:掌握父子进程之间的资源共享与异同

目的四:掌握等待子进程执行结束的方法

目的五:掌握在进程中执行另外一个可执行文件的方法

创建一个子进程 pid_t fork(void)

exec系列函数

等待子进程 pid_t waitpid(pid_t pid, int *statloc, int options)

实验内容:

基于已经实现的实验二文件拷贝(mycp)以及实验三目录遍历(myls)的内容:

  1. 改造myls程序作为父进程,其在遍历目录时,对非目录文件创建子进程运行mycp程序。
  2. mycp源文件路径是父进程myls遍历所获取的文件的路径名(通过命令行参数传递给子进程mycp),并将源文件拷贝到指定目录下(在/home目录下以自己的名字的汉语拼音创建一个目录)。
  3. 父进程myls等待子进程mycp运行结束,回收其内核空间资源,直到父进程遍历完成

程序流程:

image-20220411204021338

在实现基本功能实现的基础上,对程序功能进行扩展,以支持对目录的递归遍历并将所有目录及子目录中的文件拷贝到一个目录中

  • 如果没有命令行参数则通过getcwd获取当前工作目录
  • 如果包含一个命令行参数则通过该参数传递需要遍历的目录
  • 如果有超过一个命令行参数则出错
  • 拷贝文件及子目录下的文件

扩展内容程序流程:

image-20220411204036494

代码

代码:mydircp.c

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#define TARGET_PATH "/home/yang/test"
#define MYCP_PATH "/home/yang/Documents/Cprogram/Linux系统编程学习/mycp"
#define MYDIRCP_PATH "/home/yang/Documents/Cprogram/Linux系统编程学习/mydircp"

int main(int argc, char **argv)
{
char *dirPath = NULL;
if (argc == 1)
{
dirPath = get_current_dir_name();
}
else
{
dirPath = argv[1];
chdir(dirPath);
dirPath = get_current_dir_name();
}
dirPath = strcat(dirPath, "/");

if (access(TARGET_PATH, F_OK) == -1)
{
if (mkdir(TARGET_PATH, 0775) == -1)
{
printf("%s\n", strerror(errno));
}
}

DIR *dir = NULL;
struct stat *currentstat;
currentstat = (struct stat *)malloc(sizeof(struct stat));
if ((dir = opendir(dirPath)))
{
struct dirent *dp = NULL;
while ((dp = readdir(dir)))
{
if((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0))
{
continue;
}
if (stat(dp->d_name, currentstat) == -1)
{
printf("get stat error\n");
continue;
}
if (S_ISDIR(currentstat->st_mode))
{
pid_t pid_child;
pid_child = fork();
if (pid_child < 0)
{
printf("Error occuredon forking.\n");
}
else if (pid_child == 0)
{
if (execl(MYDIRCP_PATH, "./mydircp", strcat(dirPath, dp->d_name), NULL) == -1)
{
printf("mydircp fork error\n");
exit(-1);
}
}
}
else
{
pid_t pid_child;
pid_child = fork();
if (pid_child < 0)
{
printf("Error occuredon forking.\n");
}
else if (pid_child == 0)
{
if (execl(MYCP_PATH, "./mycp", strcat(dirPath, dp->d_name), TARGET_PATH, NULL) == -1)
{
printf("mycp fork error\n");
exit(-1);
}
}
// pid_t pid_return;
// do
// {
// pid_return = waitpid(pid_child, NULL, WNOHANG);
// sleep(1);
// printf("%d\n", pid_child);
// } while (pid_return == 0);
}
wait(NULL); //阻塞 等待子进程结束
}
closedir(dir);
}
else if (stat(dirPath, currentstat) == 0)
{
}
else
{
printf("%s:Not a directory or file\n", dirPath);
}
free(currentstat);
return 0;
}

image-20220412162210943

实验五 线程控制实验

内容 多线程CP

目的一:掌握UNIX/Linux系统创建线程的方法

目的二:理解线程启动例程的设计思想

目的三:掌握线程终止的方式

目的四:掌握线程之间共享数据的方法

目的五:掌握等待线程终止的方法

线程设计思想

多线程程序主要用于需要并发执行的场合

  • 游戏场景中:玩家通过鼠标键盘输入操作指令,控制游戏进行无多线程,则程序等待玩家指令输入,程序的动画效果停止
  • 在线视频播放存在同样的问题

image-20220412165725398

相关函数:

创建线程:

1
2
3
4
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *),
void *restrict arg);
  • 头文件 pthread.h
  • 作用:创建一个线程
  • 返回值:出错返回错误码;成功返回0

线程的终止:void pthread_exit(void *rval_ptr);

  • 头文件:pthread.h
  • 作用:线程调用后将会终止自身
  • 参数:rval_ptr指针将会传递给等待线程终止函数

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

  • 头文件:pthread.h
  • 作用:父进程阻塞直到thread子线程终止
  • 返回值:成功返回0,出错返回错误编号
  • rval_ptr:线程结束的返回值
    • 线程从启动例程返回,rval_ptr将指向返回值
    • 线程被其他线程取消,rval_ptr指向的值置为PTHREAD_CANCELED
    • 线程通过调用pthread_exit终止,rval_ptr就是pthread_exit函数中指定的参数
    • 不关注返回值,则该参数使用时使用NULL

实验内容

基于已经实现的实验二文件拷贝(mycp)以及实验三目录遍历(myls)的内容(与实验四的区别为并发单位变为线程)

  1. 改造myls程序作为从属子线程,其在遍历目录时,对非目录文件再次创建子线程运行mycp程序。
  2. mycp源文件路径是父主体线程myls遍历所获取的文件的路径名(通过命令行参数传递给子进程mycp),并将源文件拷贝到指定目录下(在/home目录下以自己的名字的汉语拼音创建一个目录)。
  3. 线程myls等待线程mycp运行结束,回收其内核空间资源,main线程等待myls遍历完成,程序结束

程序流程

image-20220412172540388

扩展一:在实现基本功能实现的基础上,对程序功能进行扩展,以支持对目录的递归遍历并将所有目录及子目录中的文件拷贝到一个目录中

  • 如果没有命令行参数则通过getcwd获取当前工作目录
  • 如果包含一个命令行参数则通过该参数传递需要遍历的目录
  • 如果有超过一个命令行参数则出错
  • 拷贝文件及子目录下的文件

扩展二:

  • mylsmycp线程并发执行(线程不等待所创建线程运行终止)遍历同时则创建多个mycp线程拷贝文件
  • 增加源文件目录结构,观察文件较多的情况下,是否能提升程序运行效率

image-20220412173029952

编译

多个源文件编译为一个可执行文件:gcc file1.c file2.c -o objfile.o

外部库的链接:线程控制API实现在线程库中lpthread

  • gcc file1.c file2.c –o objfile.o -lpthread

代码

代码:gcc mydircpt.h mydircpt.c mycpt.c mydirt.c -o mydircpt -lpthread

mydircpt.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>

#define READ_SIZE 1024
#define TARGET_PATH "/home/yang/test"

extern void *mycpt(void *arg);

extern void *mydirt(void *arg);

mydircpt.c

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
#define _GNU_SOURCE
#include "mydircpt.h"

int main(int argc, char **argv)
{
char *dirPath = NULL;
if (argc == 1)
{
dirPath = get_current_dir_name();
}
else
{
dirPath = argv[1];
if (chdir(dirPath) == -1)
{
printf("%s : Not a directory\n", dirPath);
exit(-1);
}
dirPath = get_current_dir_name();
}
dirPath = strcat(dirPath, "/");

if (access(TARGET_PATH, F_OK) == -1)
{
if (mkdir(TARGET_PATH, 0775) == -1)
{
printf("%s\n", strerror(errno));
}
}

pthread_t child;
if (pthread_create(&child, NULL, mydirt, dirPath) != 0)
{
printf("%s\n", strerror(errno));
exit(-1);
}
pthread_join(child, NULL);
return 0;
}

mycpt.c

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 "mydircpt.h"

void *mycpt(void *arg)
{
char *sourcePath = (char *)arg;
//printf("mycpt %s\n", sourcePath);
int sfd = open(sourcePath, O_RDONLY);
if (sfd == -1)
{
printf("%s:", sourcePath);
printf("%s\n", strerror(errno));
pthread_exit(NULL);
}

// char *targetPath = TARGET_PATH;
char *targetPath = (char *)malloc(strlen(TARGET_PATH) + 2);
strcpy(targetPath, TARGET_PATH);
int tfd = -1;
if (opendir(targetPath) != NULL)
{
char *ptr = NULL;
char *fname = NULL;
ptr = strrchr(sourcePath, '/');
if (!ptr)
{
ptr = sourcePath;
}
else
{
fname = (char *)malloc(strlen(ptr));
memcpy(fname, ptr + 1, strlen(ptr + 1));
ptr = fname;
}

int len = strlen(targetPath);
if (targetPath[len - 1] != '/')
{
targetPath = strcat(targetPath, "/");
}
targetPath = (char*)realloc(targetPath, strlen(targetPath) + strlen(ptr) + 1);
targetPath = strcat(targetPath, ptr);
free(fname);
ptr = NULL;
fname = NULL;
// tfd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0766);
}
tfd = open(targetPath, O_RDWR | O_CREAT | O_EXCL, 0766);
if (tfd == -1)
{
printf("%s:%s\n", targetPath, strerror(errno));
pthread_exit(NULL);
}

char buf[1024];
int size = -1;
do
{
size = read(sfd, buf, READ_SIZE);
if (size == -1)
{
printf("%s\n", strerror(errno));
pthread_exit(NULL);
}
write(tfd, buf, size);
} while (size == READ_SIZE);
printf("%s --> %s\n", sourcePath, targetPath);
close(sfd);
close(tfd);
}

mydirt.c

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
#define _GNU_SOURCE
#include "mydircpt.h"

void *mydirt(void *arg)
{
char *dirPath = (char *)arg;
chdir(dirPath);
dirPath = get_current_dir_name();
DIR *dir = NULL;
struct stat *currentstat;
currentstat = (struct stat *)malloc(sizeof(struct stat));
if ((dir = opendir(dirPath))) // 打开目录
{
struct dirent *dp = NULL;
while ((dp = readdir(dir)))
{
if ((strcmp(dp->d_name, ".") == 0) || (strcmp(dp->d_name, "..") == 0))
{
continue;
}
if (stat(dp->d_name, currentstat) == -1)
{
printf("A : %s \n", dirPath);
printf("%s \n", get_current_dir_name());
printf("%s : get stat error %s \n", dp->d_name, strerror(errno));
continue;
}
pthread_t thread_child;
char *tmp = (char *)malloc(strlen(dirPath) + strlen(dp->d_name) + 2);
strcpy(tmp, dirPath);
strcat(tmp, "/");
strcat(tmp, dp->d_name);

if (S_ISDIR(currentstat->st_mode)) // 判断是不是目录
{
if (pthread_create(&thread_child, NULL, mydirt, tmp) != 0)
{
printf("location A : Error pthread create.\n");
pthread_exit(NULL);
}
}
else // 文件
{
if (pthread_create(&thread_child, NULL, mycpt, tmp) != 0)
{
printf("location B : Error pthread create.\n");
pthread_exit(NULL);
}
}
pthread_join(thread_child, NULL); //阻塞 等待线程结束
free(tmp);
chdir(dirPath);
}
closedir(dir);
}
else
{
printf("%s : Not a directory\n", dirPath);
}
return NULL;
}

image-20220412230003981

实验六 线程同步实验

内容 哲学家进餐

目的一:了解临界资源、哲学家进餐问题、死锁等经典的操作系统同步互斥问题的基本概念

目的二:掌握利用互斥量机制实现多线程对临界资源互斥访问的方法

目的三:掌握互斥量非阻塞加锁的操作方法

目的四:掌握在保证对临界资源互斥访问的基础上通过让权等待的方式预防死锁的编程思想

哲学家进餐问题:

进餐问题主函数流程

image-20220413140109410

image-20220413140123853

哲学家任务用线程实现,并对哲学家和筷子进行编号,并通过参数传递各种编号

1
2
3
4
5
6
7
8
9
10
11
12
void philosopher(int number) {//线程例程
while(1){
printf(“philosopher %d is thinking\n”,number);
sleep(2);
takechopstick(number);
takechopstick((number+1)%5);
printf(“philosopher %d is eating\n”,number);
sleep(2);
putchopstick(number);
putchopstick((number+1)%5);
}
}

筷子(临界资源),各个哲学家(不同任务)对其进行互斥访问临界资源的访问与释放,Linux中有多种实现机制:

  • 互斥量(加锁,解锁)
  • XSI信号量集(P,V操作)
  • POSIX信号量(P,V操作)

互斥量(加锁,解锁)的使用步骤

  1. 定义一个互斥量变量( pthread_mutex_t 类型)
  2. 初始化互斥量
  3. 访问临界资源前对互斥量加锁
  4. 访问临界资源后对互斥量解锁
  5. 销毁互斥量

互斥量初始化:

  • 头文件:pthread.h
  • 定义和静态初始化互斥量变量:
  • pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER

动态初始化:

  • 函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • 参数与返回值: mutex:互斥变量指针
  • attr:设置互斥量的属性,通常采用默认属性,将attr设为NULL。成功返回0,出错返回错误码

互斥量销毁:

  • 头文件:pthread.h
  • 互斥量销毁:int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 作用:互斥量在使用完毕后,必须要对互斥量进行销毁,以释放资源参数与返回值
  • mutex:互斥量变量指针
  • 成功返回0,出错返回错误码

加锁:

  • 头文件:pthread.h
  • 函数:int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 作用:对共享资源访问之前需要加锁互斥量
  • 当调用pthread_mutex_lock时,若互斥量已被加锁,则调用线程将被阻塞(哲学家线程进入阻塞状态,等待操作系统唤醒)
  • 成功返回0,出错返回错误码

解锁:

  • 函数:int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 作用:对共享资源访问之前需要加锁互斥量
  • 成功返回0,出错返回错误码

拿筷子操作导致死锁的原因

  • 筷子需要互斥访问(临界资源)
  • 任务无法抢占其他任务已经拿起的筷子
  • 任务拿起筷子直到进餐完毕后才放下(长时间占有资源)
  • 圆桌导致可能出现循环等待

预防死锁的实现方法一: 破坏请求和保持条件

  • 任务如果无法同时拿起两支筷子,则放下已经拿起的筷子,等待一段时间再尝试
  • 利用POSIX API中的非阻塞操作实现能否拿起筷子的判断
    • pthread_mutex_trylock操作
    • sem_trywait操作
  • XSI信号量集P操作设置IPC_NOWAIT参数

头文件:pthread.h

函数:int pthread_mutex_trylock(pthread_mutex_t *mutex);

调用该函数时,若互斥量未加锁,则锁住该互斥量,返回0;

若互斥量已加锁,则函数直接返回失败(哲学家线程进入代码安排的下一步活动,思考或者饥饿)

预防死锁的实现方法二 : 破坏“循环等待条件”

  • 对哲学家编号,奇偶号哲学家拿起筷子的顺序不同
  • 创建一个服务生任务,哲学家拿起筷子时向该任务发起申请,由该任务根据当前筷子的分配情况判断系统是否由安全状态向不安全状态转换,从而允许或拒绝该次申请

实验内容

利用POSIX API在Linux系统上编写应用程序,通过多线程和互斥量机制实现哲学家进餐问题(哲学家的数量可以通过简单的配置进行修改)

  1. 首先通过阻塞加锁的操作方式实现哲学家之间(线程)对筷子(临界资源)的互斥访问,观察程序运行一段时间以后会出现的死锁状态。如果未出现死锁状态的可以通过修改哲学家数量以及修改延时设置来增大出现死锁的机率。
  2. 将互斥量的加锁操作从阻塞方式修改为非阻塞方式,通过让权等待的思想预防死锁状态的出现。

基本功能实现的基础上鼓励尝试多种思路预防死锁,包括并不限于以下思路:

  • 对哲学家编号,奇偶号哲学家拿起筷子顺序不同
  • 再创建一个任务,哲学家拿起筷子时向该任务发起申请,由该任务对当前筷子的分配情况进行判断,判定系统是否由安全状态向不安全状态转换,从而允许或拒绝该次申请

线程局部存储

线程局部存储:对于某个全局变量想做出本线程范围内的修改和读取例: errorno

static int buf[100];

  • __thread说明符让线程拥有自己对该全局变量的拷贝
  • 终止时自动释放该存储
  • 例:每个线程有自己的errorno拷贝,防止被其他线程干扰

代码

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
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM 5 // 修改NUM可以改变哲学家和筷子的数量

// static int chopstick[NUM];

pthread_mutex_t chopstick_lock[NUM];

void *philosopher(void *arg); //线程例程

int takechopstick(char name, int number); // 拿筷子
int putchopstick(char name, int number); // 放筷子

int main()
{
srand((unsigned int)time(NULL));

pthread_t pt[NUM];
char *p = (char*)malloc(NUM);
char tmp = 'A';
for (int i = 0; i < NUM; ++i)
{
p[i] = tmp + i;
pthread_create(&pt[i], NULL, philosopher, &p[i]);
pthread_mutex_init(&chopstick_lock[i], NULL);
// chopstick[i] = 0;
}

for (int i = 0; i < NUM; ++i)
{
pthread_join(pt[i], NULL);
}
free(p);
return 0;
}

void *philosopher(void *arg) //线程例程
{
char name = *((char *)arg);
int number = name - 'A';
while (1)
{
printf("philosopher %c is thinking\n", name);
sleep(rand() % 3);
// pthread_mutex_lock(&chopstick_lock);
takechopstick(name, number);
sleep(1); // 注释掉这行,大概率碰不到死锁的情况
takechopstick(name, (number + 1) % NUM);
printf("philosopher %c is eating\n", name);
sleep(rand() % 3);
putchopstick(name, number);
putchopstick(name, (number + 1) % NUM);
// pthread_mutex_unlock(&chopstick_lock);
}
}

int takechopstick(char name, int number)
{
pthread_mutex_lock(&chopstick_lock[number]);
printf("philosopher %c fetches chopstick %d\n", name, number);
}

int putchopstick(char name, int number)
{
printf("philosopher %c release chopstick %d\n", name, number);
pthread_mutex_unlock(&chopstick_lock[number]);
}

image-20220413163654676

2、无死锁情况(非阻塞情况)

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
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define NUM 6

// static int chopstick[NUM];

pthread_mutex_t chopstick_lock[NUM];

void *philosopher(void *arg); //线程例程

int takechopstick(int number);
int putchopstick(int number);

int main()
{
srand((unsigned int)time(NULL));

pthread_t pt[NUM];
char *p = (char *)malloc(NUM);
char tmp = 'A';
for (int i = 0; i < NUM; ++i)
{
p[i] = tmp + i;
pthread_create(&pt[i], NULL, philosopher, &p[i]);
pthread_mutex_init(&chopstick_lock[i], NULL);
// chopstick[i] = 0;
}

for (int i = 0; i < NUM; ++i)
{
pthread_join(pt[i], NULL);
}
free(p);
return 0;
}

void *philosopher(void *arg) //线程例程
{
char name = *((char *)arg);
int number = name - 'A';
while (1)
{
printf("philosopher %c is thinking\n", name);
sleep(rand() % 3);
// pthread_mutex_lock(&chopstick_lock);
if (!takechopstick(number))
{
continue;
}
printf("philosopher %c fetches chopstick %d\n", name, number);
sleep(1);
if (!takechopstick((number + 1) % NUM))
{
putchopstick(number);
printf("philosopher %c failed and release chopstick %d\n", name, number);
continue;
}
printf("philosopher %c fetches chopstick %d\n", name, (number + 1) % NUM);
printf("philosopher %c is eating\n", name);
sleep(rand() % 3);
putchopstick(number);
printf("philosopher %c release chopstick %d\n", name, number);
putchopstick((number + 1) % NUM);
printf("philosopher %c release chopstick %d\n", name, (number + 1) % NUM);
// pthread_mutex_unlock(&chopstick_lock);
}
}

int takechopstick(int number)
{
if (pthread_mutex_trylock(&chopstick_lock[number]) != 0)
{
return 0;
}
return 1;
}

int putchopstick(int number)
{
pthread_mutex_unlock(&chopstick_lock[number]);
}

image-20220413171407370

实验七 综合实验

内容 生成者消费者

目的一:了解生产者消费者问题中同步互斥的基本概念

目的二:掌握XSI信号量集机制

目的三:掌握XSI共享内存机制

目的四:掌握Linux中多进程协同工作的程序设计思想

生产者消费者问题:

若干任务通过共享缓冲池(包含一定数量的缓冲区)交换数据

  • “生产者”任务不断写入,而“消费者”任务不断读出
  • 任何时刻只能有一个任务可对共享缓冲池进行操作

生产者:从源(文件、网络等)读取数据放入未使用的缓冲区

消费者:从已有数据的缓冲区取走数据进行处理(计算、变换等)

image-20220413172441448

实验内容

  • 综合运用所学知识在Linux系统上编写应用程序,要求至少支持2个生产者任务(进程)和2个消费者任务(进程)的同时运行
  • 生产者任务和消费者任务之间通过XSI共享内存机制实现跨进程的共享缓冲池
  • 生产者任务和消费者任务之间通过XSI信号量集机制实现对缓冲池的互斥访问
  • 生产者与消费者之间的同步通过修改缓冲池结构中的缓冲区状态变量来实现

缓冲池结构(5个缓冲区)

1
2
3
4
5
6
7
struct BufferPool 
{
char Buffer[5][100]; //5个缓冲区
int Index[5]; //缓冲区状态
// 0 对应的缓冲区未被生产者使用,可分配但不可消费
// 1 表示对应的缓冲区以被生产者使用,不可分配但可消费
};

同步如何做到:

  • 通过缓冲池结构中的状态变量来指示(当前实验要求)
  • 通过信号量来指示未被使用缓冲区数量和已被使用缓冲区数量

互斥机制:

image-20220413172954742

  • XSI信号量集(包含1个信号量,初始值为1)
  • POSIX信号量

生产者的程序实现流程:

image-20220413173154916

消费者的程序实现流程:

image-20220413173236575

至少2个生产者任务(进程)和2个消费者任务(进程)的同时运行

Q:各个进程拥有自己的数据区域,如何访问struct BufferPool?

A:采用进程间通信的方式消息队列或者共享内存(本次实验要求)

共享内存:

  • 允许两个或更多进程访问同一块内存。当一个进程改变了这块内存中的内容的的时候,其他进程都会察觉到这个更改。
  • 但是,系统内核没有对访问共享内存的进程进行同步机制
  • 共享内存在各种进程间通信方式中具有最高的效率

共享内存相关函数:

int shmget(key_t key, size_t size, int shmflg);

  • 头文件<sys/ipc.h> <sys/shm.h>
  • 得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
  • 返回值:成功返回与key相关的共享内存标识符,出错返回-1,并设置error
  • key 标识共享内存的键值: 非零键值/IPC_PRIVATE
  • size 大于0的整数,新建的共享内存大小,以字节为单位,获取已存在的共享内存块标识符时,该参数为0,
  • shmflg IPC_CREAT||IPC_EXCL 执行成功,保证返回一个新的共享内存标识符,附加参数指定IPC对象存储权限,如|0666

int shmat(int shm_id, const void *shm_addr, int shmflg);

  • 头文件:<sys/types.h> <sys/shm.h>
  • 连接共享内存标识符为shm_id的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间
  • 返回值:成功返回所附加的共享内存地址,出错返回-1,并设置error
  • shm_id: 共享内存标识符
  • shm_addr: 指定共享内存出现在进程内存地址的什么位置,通常指定为NULL,让内核自己选择一个合适的地址位置
  • shmflg: SHM_RDONLY 为只读模式,其他参数为读写模式

void *shmdt(const void* shmaddr);

  • 头文件:<sys/types.h> <sys/shm.h>
  • 断开与共享内存附加点的地址,禁止本进程访问此片共享内存(不做删除操作)
  • 返回值:成功返回0,出错返回-1,并设置error
  • shmaddr: shmddr 连接共享内存的起始地址

int shmctl(int shmid, int cmd, struct shmid_ds* buf);

  • 头文件:<sys/types.h> <sys/shm.h>
  • 作用:控制共享内存块
  • 返回值:成功返回0,出错返回-1,并设置error
  • shmid:共享内存标识符
  • cmd:
    • IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构赋值到buf所指向的buf中
    • IPC_SET:改变共享内存的状态,把buf所指向的shmid_ds结构中的uid、gid、mode赋值到共享内存的shmid_ds结构内
    • IPC_RMID:删除这块共享内存
  • buf:共享内存管理结构体

实验内容(扩展):

  • 完善生产者和消费者程序的结束流程。例:生产者在读取完文件内容后结束自身执行,消费者在一段时间之后如果无法从缓冲池读取数据则给出提示信息,用户输入决定继续等待或退出

实验内容(选做)

  • 在信号量集中支持两个信号量,一个信号量实现对共享缓冲区的互斥访问,另外一个信号量实现对缓冲区的分配计数管理

实现多进程工作:

① 分别编写、编译生产者程序和消费者程序:在shell中分别执行生产者程序或消费者程序,每执行一次产生一个生产者任务(进程)或消费者任务(进程)

② 编写一个应用程序,通过fork创建子进程,在子进程中执行生产者的代码或消费者的代码

代码

生产者:

  • producer_input.txt文件中读取数据放入共享内存

消费者:

  • 共享内存中读取数据写入consumer_output.txt文件中

使用信号量使进程对共享内存的访问互斥

消费者

consumer7.c

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>

#define SHM_KEY 107
#define SEM_KEY 0x107
#define OUTPUT_PATH "/home/yang/Documents/Cprogram/Linux系统编程学习/consumer_output.txt"

union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};

struct BufferPool
{
char Buffer[5][100]; // 5个缓冲区
int Index[5]; // 缓冲区状态
int finish; // 是否结束
//int fd_out; //输出文件描述符
// int fd_in; //输入文件描述符
off_t in_pos; // 输入文件偏移量
int read_Index;
int write_Index;
// 0 对应的缓冲区未被生产者使用,可分配但不可消费
// 非0 为index对应Buffer的数据大小 表示对应的缓冲区已被生产者使用,不可分配但可消费
};

//分配资源
int sem_p(int semid)
{
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,单个信号量的编号为0
sem_b.sem_op = -1; //信号量操作,-1 为 P操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
return (semop(semid, &sem_b, 1));
}

//释放资源
int sem_v(int semid)
{
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,单个信号量的编号为0
sem_b.sem_op = 1; //信号量操作,1 为 V操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
return (semop(semid, &sem_b, 1));
}

//删除信号量
int del_sem(int semid)
{
union semun sem_union;
return (semctl(semid, 0, IPC_RMID, sem_union));
}

int main()
{
// 创建共享内存
int shmid = shmget((key_t)SHM_KEY, sizeof(struct BufferPool), 0666 | IPC_CREAT);
if (shmid == -1)
{
perror("consumer : shmget error\n");
exit(-1);
}
//将共享内存连接到进程
void *shmaddr = shmat(shmid, NULL, 0);
if (*((int *)shmaddr) == -1)
{
perror("consumer : shmat error\n");
exit(-1);
}
// 定义执行共享内存的结构体,取数据
struct BufferPool *shared = (struct BufferPool *)shmaddr;

// 创建信号量集,信号量数量为1
int semid;
union semun sem_union;
semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1 && errno == EEXIST)
{
semid = semget(SEM_KEY, 1, 0666); //信号量已存在,获取信号量id
// int s_tmp = semctl(semid, 0, GETVAL, sem_union);
// printf("sem : %d\n", s_tmp);
}
else //信号量刚创建,初始化信号量
{
sem_union.val = 1;
semctl(semid, 0, SETVAL, sem_union);
}

// //打开文件,用于输出
// int fd = 0;
// sem_p(semid);
// if (shared->fd_out != 0)
// {
// fd = shared->fd_out;
// }
// else
// {
// fd = open(OUTPUT_PATH, O_WRONLY | O_CREAT | O_APPEND, 0766);
// if (fd == -1)
// {
// perror("producer : open file error\n");
// exit(-1);
// }
// shared->fd_out = dup(fd);;
// }
// sem_v(semid);

int fd = open(OUTPUT_PATH, O_WRONLY | O_CREAT | O_APPEND, 0766);
if (fd == -1)
{
perror("producer : open file error\n");
exit(-1);
}

int index;

while (1)
{
sem_p(semid);
index = shared->write_Index;
if (shared->Index[index] != 0)
{
int size = shared->Index[index];
write(fd, shared->Buffer[index], size);
shared->Index[index] = 0;
// printf("consumer %s\n", shared->Buffer[index]);
shared->write_Index = (index + 1) % 5;
}
if (shared->finish && shared->Index[shared->write_Index] == 0)
{
sem_v(semid);
close(fd);
break;
}
sem_v(semid);
}
printf("consumer over\n");
//sleep(5);
if (del_sem(semid) == -1)
{
perror("Delete sem error\n");
}
if (shmdt(shared) == -1) //解除共享内存映射
{
perror(NULL);
}
if (shmctl(shmid, IPC_RMID, NULL) == -1) //删除共享内存
{
perror("Delete shm error\n");
}
return 0;
}

生产者

producer7.c

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <unistd.h>
#include <errno.h>

#define SHM_KEY 107
#define SEM_KEY 0x107
#define INPUT_PATH "/home/yang/Documents/Cprogram/Linux系统编程学习/producer_input.txt"

union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
struct seminfo *__buf; /* buffer for IPC_INFO */
void *__pad;
};

struct BufferPool
{
char Buffer[5][100]; // 5个缓冲区
int Index[5]; // 缓冲区状态
int finish; // 是否结束
//int fd_out; //输出文件描述符
// int fd_in; //输入文件描述符
off_t in_pos; // 输入文件偏移量
int read_Index;
int write_Index;
// 0 对应的缓冲区未被生产者使用,可分配但不可消费
// 非0 为index对应Buffer的数据大小 表示对应的缓冲区已被生产者使用,不可分配但可消费
};

//分配资源
int sem_p(int semid)
{
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,单个信号量的编号为0
sem_b.sem_op = -1; //信号量操作,-1 为 P操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
return (semop(semid, &sem_b, 1));
}

//释放资源
int sem_v(int semid)
{
struct sembuf sem_b;
sem_b.sem_num = 0; //信号量编号,单个信号量的编号为0
sem_b.sem_op = 1; //信号量操作,1 为 V操作
sem_b.sem_flg = SEM_UNDO; //在进程没释放信号量而退出时,系统自动释放该进程中未释放的信号量
return (semop(semid, &sem_b, 1));
}

//删除信号量
int del_sem(int semid)
{
union semun sem_union;
return (semctl(semid, 0, IPC_RMID, sem_union));
}

int main()
{
// 创建共享内存
int shmid = shmget((key_t)SHM_KEY, sizeof(struct BufferPool), 0666 | IPC_CREAT);
if (shmid == -1)
{
perror("producer : shmget error\n");
exit(-1);
}
//将共享内存连接到进程
void *shmaddr = shmat(shmid, NULL, 0);
if (*((int *)shmaddr) == -1)
{
perror("producer : shmat error\n");
exit(-1);
}
// 定义执行共享内存的结构体,取数据
struct BufferPool *shared = (struct BufferPool *)shmaddr;

// 创建信号量集,信号量数量为1
int semid;
union semun sem_union;
semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1 && errno == EEXIST)
{
semid = semget(SEM_KEY, 1, 0666); //信号量已存在,获取信号量id
}
else //信号量刚创建,初始化信号量
{
sem_union.val = 1;
semctl(semid, 0, SETVAL, sem_union);
}

// //打开文件,用于输入
// sem_p(semid);
// if (shared->fd_in == 0)
// {
// int fd = open(INPUT_PATH, O_RDONLY);
// // printf("%d\n", fd);
// // sleep(100);
// if (fd == -1)
// {
// perror("producer : open file error\n");
// exit(-1);
// }
// shared->fd_in = dup2(fd, 107);
// printf("%d\n", shared->fd_in);
// }
// sem_v(semid);

int fd = open(INPUT_PATH, O_RDONLY);
if(fd == -1)
{
perror("producer : open file error\n");
exit(-1);
}

// printf("%d\n", shared->Index[0]);
// printf("%d\n", shared->Index[1]);
// printf("%d\n", shared->Index[2]);
// printf("%d\n", shared->Index[3]);
// printf("%d\n", shared->Index[4]);
// printf("%d\n", shared->finish);

int index;
while (1)
{
sem_p(semid);
int index = shared->read_Index;
if (shared->Index[index] == 0)
{
lseek(fd, shared->in_pos, SEEK_SET);
int size = read(fd, shared->Buffer[index], 20);
if (size == 0)
{
shared->finish = 1;
sem_v(semid);
close(fd);
break;
}
off_t currpos = lseek(fd, 0, SEEK_CUR);
shared->in_pos = currpos;
shared->Index[index] = size;
shared->read_Index = (index + 1) % 5;
//printf("%s", shared->Buffer[index]);
}
sem_v(semid);
}
// printf("producer over\n");
//sleep(5);
if (del_sem(semid) == -1)
{
perror("Delete sem error\n");
}
if (shmdt(shared) == -1) //解除共享内存映射
{
perror(NULL);
}
if (shmctl(shmid, IPC_RMID, NULL) == -1) //删除共享内存
{
perror("Delete shm error\n");
}
return 0;
}

创建多进程

pd_cs7.c 创建3个生产者进程和3个消费者进程

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 <stdlib.h>
#include <unistd.h>

#define PRODUCER "/home/yang/Documents/Cprogram/Linux系统编程学习/producer7"
#define CONSUMER "/home/yang/Documents/Cprogram/Linux系统编程学习/consumer7"

int main()
{
pid_t producer[3];
pid_t consumer[3];

for(int i = 0; i < 3; ++i)
{
producer[i] = fork();
if(producer[i] == -1)
{
perror("producer fork error\n");
}
else if(producer[i] == 0)
{
execl(PRODUCER, "./producer7", NULL);
}
consumer[i] = fork();
if(consumer[i] == -1)
{
perror("consumer fork error\n");
}
else if(consumer[i] == 0)
{
execl(CONSUMER, "./consumer7", NULL);
}
}
return 0;
}

运行截图

producer_input.txt文件内容

image-20220415153341103

运行pd_cs创建3个生产者和3个消费者(图中有报错,没有影响)

image-20220415153705171

consumer_output.txt文件中写入的内容和producer_input.txt一致

image-20220415153753914

创建的共享内存和信号量都在程序运行完时删除掉了(上面的报错就是因为重复删除)

image-20220415153920400

问题

producer7.cconsumer7.c 的 首尾是相似的

  • 创建共享内存(如果已存在则返回id)shmget
  • 连接共享内存和进程shmat
  • 创建信号量(如果已存在则返回id)semget
  • 初始化信号量为1semctl
  • ……
  • 删除信号量
  • 解除共享内存连接
  • 删除共享内存

Q1:因为进程创建多个,则初始化信号变量会有多次,这就会导致问题出现(如A进程进行了P操作,semval从1变为0,而B进程刚开始运行并初始化信号量semval为1,则A进程进行V操作后,semval就变为2了)

A1:创建信号量时使用IPC_EXCL,信号量已存在会直接返回并报错

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建信号量集,信号量数量为1
int semid;
union semun sem_union;
semid = semget(SEM_KEY, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1 && errno == EEXIST)
{
semid = semget(SEM_KEY, 1, 0666); //信号量已存在,获取信号量id
}
else //信号量刚创建,初始化信号量
{
sem_union.val = 1;
semctl(semid, 0, SETVAL, sem_union);
}

Q2:使用dupdup2实现不同进程间共享一个文件发现并不能成功,准确的说不能共享一张文件表,所以文件的偏移量不能同步

A2:每回从producer_input.txt读取数据前,从共享内存读取当前的偏移量in_pos,读取数据后,用lseek函数获取当前偏移量,更新到共享内存的in_pos,初始的偏移量为0,输出文件是直接在末尾追加内容,不需要保存偏移量。

Q3:共享内存使用char Buffer[5][100];保存读取的数据,int Index[5];保存对应字符数组数据量的大小。但读取数据放入Buffer,和从Buffer读取数据写入文件需要同步,不然会导致顺序错乱。

A3:read_Index保存当前读取的数据要放入的Bufferwrite_Index保存当前从哪个Buffer读取数据写入consumer_output.txt

Q4:怎样确保生产者读完数据但消费者还没写完数据时,生产者不会把共享内存删除。

A4:???不清楚。上面的代码没有这方面的处理,但是好像没出问题?猜测是因为引用计数的原因,必须所有的引用都释放掉,才能删除?

欢迎关注我的其它发布渠道