实验0 Linux基础
【实验目的】
1.掌握Linux基本命令接口的使用方法与常用命令;
2.掌握在Linux环境下如何编辑、编译和运行一个C语言程序。
3.学会利用gcc、gdb编译、调试C程序。
【预习内容】
1.预习Linux虚拟机的安装,并安装一个Linux虚拟机(redhat,ubuntu或fedora,建议使用ubuntu)。
2.预习常用的SHELL命令。
4.预习Linux下C程序编辑、编译和运行过程。
【实验内容】
一、登陆Linux
输入用户名: root ,输入密码: 123456 ,进入redhat5图形桌面环境。
二、熟悉Linux图形用户界面(GUI)
目前,Linux上有两种GUI:KDE和GNOME,GNOME用户量比较大,主要的原因是KDE4经常崩溃,据了解,目前KDE5还可以。
图形用户界面,由于比较简单,请各位同学自学。
启动终端模拟器
GNOME终端模拟器用一个窗口来模拟字符终端的行为。终端常常被称为命令行或者 shell,Linux 中绝大部分工作都可以用命令行完成。要启动一个终端,可以选择 应用程序 → 附件 → 终端。
三、练习常用的Shell命令。(重点)
当用户登录到字符界面系统或使用终端模拟窗口时,就是在和称为shell的命令解释程序进行通信。当用户在键盘上输入一条命令时,shell程序将对命令进行解释并完成相应的动作。这种动作可能是执行用户的应用程序,或者是调用一个编辑器、GNU/Linux实用程序或其他标准程序,或者是一条错误信息,告诉用户输入了错误的命令。
1.目录操作
mkdir abc 创建一个目录abc
cd abc 将工作目录改变到abc
cd 改变当前目录到主目录
ls 列出当前目录的内容
ls -l 输出当前目录内容的长列表,每个目录或文件占一行
pwd 显示当前目录的全路径
2.文件显示实用程序
cat mx.c 显示mx.c文件内容
more mx.c 分屏显示mx.c内容
tail mx.c 显示文件后几行
cat file1 file2 连接file1 和file2
head filename 显示文件filename的开始10行
wc filename 统计文件filename中的行数、单词数和字符数
od 文件 查看非文本文件
3.文件管理实用程序
cp file1 file2 将文件1复制到文件2
mv file1 file2 将文件重命名为file2
rm filename 删除文件filename
rm -i filename 请求用户确认删除
4.数据操作实用程序
tty 显示当前终端的路径和文件名
who 显示当前登录用户的列表
sort filename 显示文件filename中的行的排序结果
spell filename 检查文件filename中的拼写错误
5. 改变文件或目录的存取权
我们知道,每个文件或目录对系统的3种人,即文件的主人、同组人和其他人各有3种存取权。这3种存取权是读权、写权和执行权。在文件初建时,文件的主人对其只有读、写权,同组人只有读权,其他人无任何存取权。这种存取权在系统种要根据需要进行变化,用chmod命令来实现。Chmod命令有两种格式,一种是符号方式,一种是数字方式。现在先介绍第一种:
chmod 谁 操作符 许可权 文件名(目录名)……
上面格式中各项的具体内容如下:
谁 操作符 许可权
u(user: 文件主人) + r(read: 读权)
g(group: 同组人) — w(write: 写权)
o(other: 其他人) = x(excute: 执行权)
a(all: 所有人)
要给系统中某种人增加某种存取权可用“+”操作,要取消某种存取权可用“-“操作;而”=“操作则表示给表达式中指定的人以指定的存取权而取消其以前的存取权,下面简单举例进行说明:
$ shmod u+x file
表示给文件的主人增加对文件FILE的执行权。
$ chmod g=x file
表示给同组人对flie以执行权,同时取消其原有权利。
$ chmod u+x,g=x file
6.其他实用程序
date 输出系统日期和时间
cal 显示本月的日历。cal 2002 显示2002年的日历
clear 清除终端屏幕
history 显示你以前执行过的命令的列表
man 显示实用程序的有用信息,并提供该实用程序的基本用法
echo 读取参数并把它写到输出
四、目录和文件系统
Linux 和 Unix 文件系统被组织成一个有层次的树形结构。文件系统的最上层是 /,或称为 根目录。在 Unix 和 Linux 的设计理念中,一切皆为文件——包括硬盘、分区和可插拔介质。这就意味着所有其它文件和目录(包括其它硬盘和分区)都位于根目录中。 例如:/home/jebediah/cheeses.odt 给出了正确的完整路径,它指向 cheeses.odt 文件,而该文件位于 jebediah 目录下,该目录又位于 home 目录,最後,home 目录又位于根(/) 目录下。 在根 (/) 目录下,有一组重要的系统目录,在大部分 Linux 发行版里都通用。直接位于根 (/) 目录下的常见目录列表如下:
• /bin - 重要的二进制 (binary) 应用程序
• /boot - 启动 (boot) 配置文件
• /dev - 设备 (device) 文件
• /etc - 配置文件、启动脚本等 (etc)
• /home - 本地用户主 (home) 目录
• /lib - 系统库 (libraries) 文件
• /lost+found - 在根 (/) 目录下提供一个遗失+查找(lost+found) 系统
• /media - 挂载可移动介质 (media),诸如 CD、数码相机等
• /mnt - 挂载 (mounted) 文件系统
• /opt - 提供一个供可选的 (optional) 应用程序安装目录
• /proc - 特殊的动态目录,用以维护系统信息和状态,包括当前运行中进程 (processes) 信息。
• /root - root (root) 用户主文件夹,读作“slash-root”
• /sbin - 重要的系统二进制 (system binaries) 文件
• /sys - 系统 (system) 文件
• /tmp - 临时(temporary)文件
• /usr - 包含绝大部分所有用户(users)都能访问的应用程序和文件
• /var - 经常变化的(variable)文件,诸如日志或数据库等
五.打开PROC目录了解系统配置
把/proc作为当前目录,就可使用ls命令列出它的内容。
/proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 。这个伪文件系统让你可以和内核内部数据结构进行交互,获取有关进程的有用信息,在运行中改变设置 (通过改变内核参数)。 与其他文件系统不同,/proc 存在于内存之中而不是硬盘上。
1.察看 /proc 的文件
/proc 的文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的状态等信息。大部分 /proc 中的文件和目录提供系统物理环境最新的信息。尽管 /proc 中的文件是虚拟的,但它们仍可以使用任何文件编辑器或像’more’, 'less’或 'cat’这样的程序来查看。
2.得到有用的系统/内核信息
/proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。下面是一些重要的文件:
• /proc/cpuinfo - CPU 的信息 (型号, 家族, 缓存大小等)
• /proc/meminfo - 物理内存、交换空间等的信息
• /proc/mounts - 已加载的文件系统的列表
• /proc/devices - 可用设备的列表
• /proc/filesystems - 被支持的文件系统
• /proc/modules - 已加载的模块
• /proc/version - 内核版本
• /proc/cmdline - 系统启动时输入的内核命令行参数
proc 中的文件远不止上面列出的这么多。想要进一步了解的读者可以对 /proc 的每一个文件都’more’一下 。
3.有关运行中的进程的信息
/proc 文件系统可以用于获取运行中的进程的信息。在 /proc 中有一些编号的子目录。每个编号的目录对应一个进程 id (PID)。这样,每一个运行中的进程 /proc 中都有一个用它的 PID 命名的目录。这些子目录中包含可以提供有关进程的状态和环境的重要细节信息的文件。
/proc 文件系统提供了一个基于文件的 Linux 内部接口。它可以用于确定系统的各种不同设备和进程的状态。对他们进行配置。因而,理解和应用有关这个文件系统的知识是理解你的 Linux 系统的关键。
六、熟悉vim编辑器
在编写文本或计算机程序时,需要创建文件、插入新行、重新排列行、修改内容等,计算机文本编辑器就是用来完成这些工作的。
Vim编辑器的两种操作模式是命令模式和输入模式(如图2所示)。当vim处于命令模式时,可以输入vim命令。例如,可以删除文本并从vim中退出。在输入模式下,vim将把用户所输入的任何内容都当作文本信息,并将它们显示在屏幕上。
vi的工作模式见图2所示。
⑴命令模式
在输入模式下,按ESC可切换到命令模式。命令模式下,可选用下列指令离开vi:
命令 作 用
:q! 离开vi,并放弃刚在缓冲区内编辑的内容
:wq 将缓冲区内的资料写入当前文件中,并离开vi
:ZZ 同wq
:x 同wq
:w 将缓冲区内的资料写入当前文件中,但并不离开vi
:q 离开vi,若文件被修改过,则要被要求确认是否放弃修改的内容,此指令可与:w配合使用
命令模式下光标的移动 :
命 令 作 用
h或左箭头 左移一个字符
J 下移一个字符
k 上移一个字符
l 右移一个字符
0 移至该行的首
$ 移至该行的末
^ 移至该行的第一个字符处
H 移至窗口的第一列
M 移至窗口中间那一列
L 移至窗口的最后一列
G 移至该文件的最后一列
W, W 下一个单词 (W 忽略标点)
b, B 上一个单词 (B 忽略标点)
- 移至下一列的第一个字符处
- 移至上一列的第一个字符处
( 移至该句首
) 移至该句末
{ 移至该段首
} 移至该段末
nG 移至该文件的第n列
⑵输入模式
输入以下命令即可进入vi输入模式:
命 令 作 用
a(append) 在光标之后加入资料
A 在该行之末加入资料
i(insert) 在光标之前加入资料
I 在该行之首加入资料
o(open) 新增一行于该行之下,供输入资料用
O 新增一行于该行之上,供输入资料用
Dd 删除当前光标所在行
X 删除当前光标字符
X 删除当前光标之前字符
U 撤消
• 重做
F 查找
s 替换,例如:将文件中的所有"FOX"换成"duck",用":%s/FOX/duck/g"
ESC 离开输入模式
启动vim命令:
命令 作用
vim filename 从第一行开始编辑filename文件
vim +filename 从最后一行开始编辑filename文件
vim -r filename 在系统崩溃之后恢复filename文件
vim -R filename 以只读方式编辑filename文件
更多用法见 info vi。
vim 下程序录入过程:
①$ vim aaa.c ↙ 进入vim命令模式
② i ↙ 进入输入模式输入 C源程序(或文本)
③ ESC ↙ 回到命令模式
④ ZZ ↙ 保存文件并推出vim
⑤ CAT aaa.c ↙ 显示aaa.c 文件内容
七、熟悉gcc编译器
GNU/Linux中通常使用的C编译器是GNU gcc。编译器把源程序编译生成目标代码的任务分为以下4步:
a. 预处理,把预处理命令扫描处理完毕;
b. 编译,把预处理后的结果编译成汇编或者目标模块;
c. 汇编,把编译出来的结果汇编成具体CPU上的目标代码模块;
d. 连接,把多个目标代码模块连接生成一个大的目标模块;
1.使用语法:
gcc [ option | filename ]…
其中 option 为 gcc 使用时的选项,而 filename 为 gcc要处理的文件。
2.GCC选项
GCC的选项有很多类,这类选项控制着GCC程序的运行,以达到特定的编译目的。
⑴全局选项(OVERALL OPTIONS)
全局开关用来控制在“GCC功能介绍”中的GCC的4个步骤的运行,在缺省的情况下,这4个步骤都是要执行的,但是当给定一些全局开关后,这些步骤就会在 某一步停止执行,这产生中间结果,例如可能你只是需要中间生成的预处理的结果或者是汇编文件(比如你的目的是为了看某个CPU上的汇编语言怎么写)。
① –x language
对于源文件是用什么语言编写的,可以通过文件名的后缀来标示,也可以用这开关。指定输入文件是什么语言编写的,language 可以是如下的内容
a. c
b. objective-c
c. c-header
d. c++
e.cpp-output
f.assembler
g.assembler-with-cpp
②–x none
把-x开关都给关掉了。
③ –c
编译成把源文件目标代码,不做连接的动作。
④–S
把源文件编译成汇编代码,不做汇编和连接的动作。
⑤–E
只把源文件进行预处理之后的结果输出来。不做编译,汇编,连接的动作。
⑥ –o file (常用)
指明输出文件名是file。
⑦–v
把整个编译过程的输出信息都给打印出来。
⑧–pipe
由于gcc的工作分为好几步才完成,所以需要在过程中生成临时文件,使用-pipe就是用管道替换临时文件。
⑵ 语言相关选项(Language Options)
用来处理和语言相关的选项。
①–ansi
这个开关让GCC编译器把所有的GNU的编译器特性都给关掉,让你的程序可以和ansi标准兼容。
②–include file
在编译之前,把file包含进去,相当于在所有编译的源文件最前面加入了一个#include 语句,
③–C
同-E参数配合使用。让预处理后的结果,把注释保留,让人能够比较好读它。
⑶连接开关(Linker Options)
用来控制连接过程的开关选项。
① –llibrary
连接库文件开关。例如-lugl,则是把程序同libugl.a文件进行连接。
② –lobjc
这个开关用在面向对象的C语言文件的库文件处理中。
③ –nostartfiles
在连接的时候不把系统相关的启动代码连接进来。
④ –nostdlib
在连接的时候不把系统相关的启动文件和系统相关的库连接进来。
⑤–static
在一些系统上支持动态连接,这个开关则不允许动态连接。
⑥shared
生成可共享的被其他程序连接的目标模块。
⑷目录相关开关(Directory Options)
用于定义与目录操作相关的开关。
–Ldir
搜寻库文件(*.a)的路径。
⑸调试开关(Debugging Options)
–g
把调试开关打开,让编译的目标文件有调试信息。
–V version
用来告诉编译器使用它的多少版本的功能,version参数用来表示版本。
八、掌握Linux下C程序编辑运行过程(重点)
Linux下编写C程序要经过以下几个步骤:
⑴启动常用的编辑器,键入C源程序代码。
例如,点击应用程序/附件/文本编辑器,进入编辑环境,输入C源程序,保存并命名为hello.c
include <stdio.h>
void main(void)
{
Printf(“Hello world!\n”);
}
⑵编译源程序
点击应用程序/附件/终端,进入命令行。用gcc编译器对C源程序进行编译,以生成一个可执行文件。方法:
gcc -o hello.out hello.c ↙
⑶运行可执行文件
•/hello.out ↙
注:命令行中 -o选项表示要求编译器输出可执行文件名为hello.out文件,hello.c是源程序文件。
关于Makefile文件
Make是Linux下一个常用的自动化编译管理工具,可以简化和自动化多模块程序的编译过程
-
语法规则
目标:依赖文件集合
命令1
命令2 -
命令列表中的每条命令必须以TAB键开始,不能使用空格
第一条规则的目标成为默认目标 例如main
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
九、UNIX系统调用及应用(重点)
- UNIX系统调用概述
在高级语言程序设计中,UNIX系统调用是系统为用户程序提供的一组访问系统内核(kernel)的函数调用;如用户在C语言程序设计中使用系统调用,则可在C语言程序设计中可直接应用系统调用的函数名;
例: 文件的随机存取。利用系统调用Lseek来定位文件的读写位置,可实现对文件的随机存取。
Lseek的格式是
long lseek (fd,offset,whence)
long offset;int fd,whence;
其中,fd是被打开的文件号,offset和whence结合起来定位文件的读写位置,若whence=0,则读写位置offset为(如offset=512,则下一个读写就从第512个字节开始);若whence=1,则读写位置被指定为当前位置加offset(可正可负);若whence=2,则读写位置被指定为文件末尾加offset。
下面的C函数可从一个文件的任意位置开始读出指定字节数的信息。
Get (fd,position,buf,n) /read n bytes from position to buf/
Int fd,n,position;
Char *buf;
Int fd,n;
Long position;
{
lseek (fd,position,0);
rerurn (read(fd,buf,n));
}
用户在应用程序中通过系统调用可充分的利用操作系统提供的系统功能和服务,但是其程序执行的效率降低,原因如下:
•在启动调用系统的时候会有一段不可避免的延迟,这是因为Linux必须把作为用户级的进程请求化为内核级的进程,然后再把结果转会用户级的进程,这其中的花费比一般的函数调用要多得多。
•硬件设备往往对一次输入、输出数据的尺寸有一定的限制;比如磁带设备常常有一个最小块限制,假设是10KB ,如果你想向磁带机写入8KB 的数据,磁带机仍然会写入10KB的数据,因此在磁带机上产生了一段缺口。
-
进程与线程
⑴概念
进程: 进程是在一地址空间上执行的单一的指令序列。
线程: 在进程中可以有多个指令序列并发执行,把每一个执行序列称为线程。
⑵查阅进程
我们使用ps命令查阅当前系统的进程。下面是个典型的输出:
$ps
PID TTY STAT TIME COMMAND
71 1 S 0:00 -bash
72 2 S 0:00 -bash
73 3 S 0:00 /sbin/agetty 38400 tty3 linux
74 4 S 0:00 /sbin/agetty 38400 tty4 linux
75 5 S 0:00 /sbin/agetty 38400 tty5 linux
76 6 S 0:00 /sbin/agetty 38400 tty6 linux
87 p0 S 0:00 -bash
102 p0 S 0:00 ./mysqld – Sg – – port =1989
103 p0 S 0:00 ./mysqld – Sg – – port =1989
104 p0 S 0:00 ./mysqld – Sg – – port =1989
108 p0 S 0:00 ./mysqld – Sg – – port =1989
162 p1 S 0:00 -bash
332 p1 S 0:00 ps这一输出说明mysql 数据库正在运行。其中:
PID ----进程号
TTY----启动进程的终端
STAT----进程当前的状态
TIME----进程消耗CPU的时间
COMMAND----启动进程的命令(包括命令参数)
⑶创建新的进程
我们将介绍三种创建进程的方法:
• system函数
在一个程序中创建一个进程的最简单的方法就是使用sysytem函数。
#include <stdlib.h>
int system(const char *string);
system函数执行由string参数指定的命令(可以包含参数),并将程序设置为等待状态直到此命令结束,再继续执行下面的代码。
下面就利用system函数编写一个程序,在程序中调用外部命令ps。
#include <stdlib.h>
#include <stdio.h>
int main()
{
printf(“Running ps with system\n”);
system(“ps -ax”);
print(“Done.\n”);
exit(0);
}
上机实习步骤:
输入编辑源程序:
使用全屏幕编辑程序vi来输入和编辑源程序(vi的使用方法见后面内容)
② 编译源程序:
$ cc -o system system.c
运行源程序
$./system
屏幕显示:
Running ps with system
PID TTY STAT TIME COMMAND
1 ? S 0:03 init
2 ? SW 0:00 (kflushd)
…….
162 p1 S -bash
710 p1 S ./system
711 p1 R ps -ax
Done.
可以看到在系统进程表中的710和711是我们执行程序时产生的,并且ps处于运行
状态,而./system处于睡眠状态。
练习:将上面源程序的第六行改为:
system(“ps –ax &”);
时,执行结构是怎样的?为什么?
• exec函数家族
exec函数家族的函数都以exec开头,且功能类似;它比system函数低级得多。它是通过覆盖自身执行代码的方法来执行外部命令的。
如:
include <unistd.h>
extern char * *environ;
int execl(const char *path,const char * arg , …, (char *) 0);
int execlp(const char *file ,const char * arg , …, (char *) 0);
int execv(const char *path, char * const argv[ ]);
下面就利用execlp函数编写一个程序,在程序中调用外部命令ps。
#include <unistd.h>
#include <stdio.h>
int main()
{
printf(“Running ps with execlp \n”);
execlp(“ps”, “ps”, “-ax”,0);
printf(“Done.\n”);
exit(0);
}
以文件名pexec.c 存盘,并编译运行:
$./pexec
屏幕显示:
Running ps with system
PID TTY STAT TIME COMMAND
1 ? S 0:03 init
2 ? SW 0:00 (kflushd)
…….
161 p1 S -bash
811 p1 R ps -ax
Done.
•系统调用 fork
fork是系统提供的建立进程的系统调用,其工作原理是:fork创建一个新的进程,新进程拥有系统分配的具有唯一特性的进程号,然后,父进程继续执行下去(父进程就是调用fork的进程)。新的进程复制了父进程的数据空间(变量),文件描述符和文件流;
fork调用将返回进程号给父进程,若返回为0,则表明创建进程成功,进入子进程执行;若返回为-1,则表示出错,一般是由于进程数超过了系统允许创建的子进程最大限制。
其调用框架为:
#include <sys/types.h>
#include <unistd.h>
main ()
{
pid_t new_pid;
new_pid=fork();
switch (new_pid)
{
case –1 😕/Error
break;
case 0: //come in child
break;
default; //in parent
break;
}
例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t pid;
char *message;
int n;
printf(“fork program starting \n”);
pid=fork();
switch(pid)
{
case -1 : exit(1);
case 0 : message=“This is the child”;
n=5;
break;
default : message=“This is the parent”;
n=3;
break;
}
for (; n>0; n--)
{
puts(message);
sleep(1);
}
exit(0);
}
在上面的程序中,我们创建了一个子进程,此时有两个子进程并发执行,其中子进程输出一个字符串五次,父进程输出另一个字符串三次。
将上例以文件名fork编辑、编译和运行。结果如下:
$./fork
fork program starting
This is then parent
This is then child
This is then parent
This is then child
This is then parent
This is then child
$ This is then child {注意此时父进程以执行完毕,但子进程为完成}
This is then child
如果对上例感到不理解,可以把打印语句分别放到case 中给变量n 赋数值之后、break之前,并把原来的switch之后的打印语句删除,这样可能较清晰一些。
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t pid;
char *message;
int n;
printf("fork program starting \n");
pid=fork();
switch(pid)
{
case -1 : exit(1);
case 0 : message="This is the child";
n=5;
for (; n>0; n--)
{
puts(message);
sleep(1);
}
break;
default : message="This is the parent";
n=3;
break;
for (; n>0; n--)
{
puts(message);
sleep(1);
break;
}
}
exit(0);
}
注意:子进程一旦被创建,它就脱离父进程独立运行;在上例中我们看到父进程先于子进程结束,这在实际应用中不利,如主控程序只有在所有子进程完成后才能中止。为了解决此问题,可使用系统调用wait,来让父进程处于等待状态,直到子进程结束才完成。有关细节请同学们参考其他书籍完成。
⑷创建新的线程
进程可以提高程序的并发执行的程度,但仍然存在如下两个问题:
•fork是昂贵的,内存映像要从父进程复制到子进程。即使现在实现的写时复制技术(copy – on – write)也只能略微减轻这种负担。
•fork 创建子进程后,需要用进程通讯的方法在父、子进程间传递信息。Fork之前的信息时容易传递的,因为子进程开始的时候复制了父进程的数据空间;但是从子进程返回信息给父进程是很麻烦的一件事。
线程机制可以解决上面的两个问题。
线程由称为轻进程,线程的开销比进程的小得多,因此创建线程也快得多,通常要比创建进程快10-100倍。一个进程中的所有线程共享全局的内存(变量),这使得线程的信息共享较容易。但是其简易性也带来了另一个棘手的问题,即同步。
线程不仅共享全局变量而且也共享:
•进程指令
•大多数数据
•打开的文件
•信号处理程序和信号处置
•当前目录
•用户ID和组ID
但每个线程都有属于自己的:
•线程ID
•寄存器集合(包括程序计数器和栈指针)
•栈(用于存放局部变量和返回地址)
•erron(错误代码)
•信号掩码
•优先级
linux系统提供了pthread函数库来编写线程的程序,下面我们简单的加以介绍:
函数库pthread中的函数都以pthead_开头。
#include <pthread.h>
int pthread_creat(pthread_t *tid,const pthread_attr_t *attr,void *(*func(void 8),void *arg);
int pthread_join(pthread_t tid,void * *status);
int pthread_exit(void *status);
分别解释如下:
• 函数pthread_create用来创建一个线程。一个进程中的每个线程都由一个线程ID来标识,其数据类型是pthread_t(通常是unsigned int)。如果函数成功执行将返回一个线程的ID ,而pthread_create的第一个参数就是用来存储该值的。
每个线程都有很多属性:优先级、起始栈大小、是否作为守护线程等。而pthread_create函数的第二个参数就是用来设置这些属性的,如果为空,则使用缺省值。
最后,当创建一个线程的时候,我们要给它指定一个将要执行的函数。这个函数由第三个参数给出,而此函数的参数由第四个参数指定。
线程创建成功返回0,出错返回非零值赋给errno。
• 函数pthread_join用来等待一个线程的终止。如果把线程和进程加以对比,那么pthread_create类似于forkj,而pthread_join相当于waitpid。
• 函数pthread_exit用于终止线程,如果线程未脱离,其线程ID和状态信息将一直保留到调用进程中的某个其他线程调用pthread_join。有两种方法终止线程:
启动线程的函数(pthread_create 的第三个参数)返回。
如果进程的main函数返回或者任何一个线程调用了exit,进程将终止,自然所有的线程也随之终止。
下面的例子是一个简单的线程创建的程序,更进一步的内容可参考其他书籍。
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *process(void *arg)
{
int i;
fprintf(stderr,“Starting process %s \n”,(char )agr);
for (i=0;i<10000;i++)
{
write(1,(char) arg,1);
}
return NULL;
}
int main()
{
int retcode;
pthread_t th_a,th_b;
void * retval;
retcode=pthread_create(&th_a,NULL,process,“a”);
if (retcode!=0) fprintf(stderr,“creat a failed %d \n”,retcode);
retcode=pthread_create(&th_a,NULL,process,“b”);
if (retcode!=0) fprintf(stderr,“creat b failed %d \n”,retcode);
retcode=pthread_join(th_a,&retval);
if (retcode!=0) fprintf(stderr,“join a failed %d \n”,retcode);
retcode=pthread_join(th_b,&retval);
if (retcode!=0) fprintf(stderr,“join a failed %d \n”,retcode);
return 0;
}
上面的程序很简单,它先输出10000个a,然后输出10000个b。
注意:有些发行版的linux不带有pthread库,需要自己安装,方法为:把头文件复制到/usr/include 目录下,而将libpthread.a复制到/usr/lib目录下,共享库libpthread.so.x.xx复制到/usr/lib目录下。再来编译运行。
$ cc –o pthread pthread.c –lpthread
$./pthread
屏幕显示:
aaaa…
….bbb
bbb…
….
bb…
$
二、实习题
在LINUX环境下建立、修改、连接和执行上述例序;打印源程序和执行结果;最后撤消所有文件。
编程执行下列命令序列。
cp /etc/fstab
sort fstab –o myfstab
cat myfstab
参考代码如下:
main()
{
if (! fork) )
{
execlp(“cp”,”cp”,”/ect/fstab”,”.”,0);
printf(“error executing cp\n”);
exit(1);
}
wait(0);
if (! fork) )
{
execlp(“sort”,”sort”,”fstab”,”-o”,”myfstab”,0);
printf(“error executing sort\n”);
exit(1);
}
wait(0);
execlp(“cat”,”cat”,”myfstab”,0);
printf(“error executing cat\n”);
exit(1);
}
注意:此出没有检查fork的返回值,请同学们完成。
【实验报告】
本实验不写实验报告