一、编写C语言程序:输出两行文字“how are you”,在Linux下编辑,编译,运行。
实验步骤: 1. 编辑:$gedit hello.c 进入gedit编辑程序,保存。“gedit代表的为编辑器,比如我用的vim就可以使用命令$vim hello.c
2. 编译:$gcc –o hello hello.c。没有错误出现命令提示符,ls命令查看生成hello可执行程序。
3, 运行:$./hello 看到程序结果。
二、编辑以下程序loop.c
#include <stdio.h>
main()
{ while (1) { } }
1. 输入程序
2. 编译 gcc –o loop loop.c
3.(前台)运行 ./loop 按Ctrl+C终止
4. 后台运行 ./loop & (可多次使用该命令)
5. 多次使用ps命令查看进程状态 注意loop的运行时间 注意ps -l命令和loop命令的父进程号均为shell进程
6. 使用kill命令控制该进程 暂停 kill –STOP <该进程的进程号>
恢复 kill –CONT <该进程的进程号>
终止 kill –KILL <该进程的进程号>
三、文件操作
在当前目录下建立自己的子目录mydir。 $ mkdir mydir
查看创建的子目录 $ ls -l
进入子目录mydir $ cd mydir
再创建子目录sub $ mkdir sub
查看创建的子目录 $ ls -l
删除部分子目录sub $ rmdir sub
回退到根目录 $ cd
复制文件file.c到newdir目录。 $ cp file.c newdir
删除文件file.c。 $ rm file.c
四:GCC
gcc命令的基本用法如下:
格式:gcc [选项] 源文件 [目标文件]
当不用任何选项编译一个程序时,gcc将建立(假定编译成功)一个名为a.out的可执行文件。
例如, $ gcc hello.c /*生成可执行程序名为a.out*/ 也可用-o选项来为即将产生的可执行文件指定一个文件名来代替a.out。
例如: $ gcc –o hello hello.c /*生成可执行程序名为hello*/
$ gcc hello.c –o hello /*生成可执行程序名为hello*/
执行程序 格式:
./可执行文件名 例: $./a.out /*运行程序a.out*/
$./count /*运行程序count*/
五:fork()
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
#include<sys/types.h>
#include<unistd.h>#include<stdio.h>
#include<string.h>
main(){
int p;
p=fork();printf("fork 1\n");
if(p>0){
fork();
printf("fork2\n");
}
else{
fork();
printf("fork3\n");
fork();
printf("fork4\n");
}
sleep(20);
return 0;
}
运行结果:
创建一个新进程。
系统调用格式: p=fork( );
参数定义: int p;
返回值:子进程中为0,父进程中为子进程ID,出错为-1。
fork( )返回值pid意义如下: p=0:在子进程中,p变量保存的fork()返回值为0,表示当前进程是子进程。
p>0:在父进程中,p变量保存的fork()返回值为子进程的ID值(进程唯一标识符)。
p=-1:创建失败。
操作系统在内存中建立一个新进程,所建的新进程是调用fork()父进程的副本,称为子进程。
子进程是父进程的一个拷贝。即子进程从父进程得到了数据段和堆栈段的拷贝,需要分配内存 。
fork返回后,子进程和父进程都从调用fork函数的下一条语句开始执行。
父进程与子进程并发执行。 父进程与子进程的不同:fork的返回值不同——父进程中的返回值为子进程的进程号,而子进程为0。
fork函数创建子进程的执行可以简单用如下几点概括:
产生一个新的PCB;
父子共享代码区,子复制父的数据区;
返回值:父返子进程pid,子返为0;
fork系统调用的用途:
⑴ 一个进程希望复制自身,从而父子进程能同时执行不同段的代码。
⑵ 进程想执行另外一个程序
代码段:存放程序代码的数据;
堆栈段:存放子程序的返回地址,子程序的参数以及程序的局部变量
数据段:存放程序的全局变量,常数以及动态数据分配的数据空间。
一个程序一旦调用fork()函数,系统就为一个新的进程准备三个段:代码段,堆栈段和数据段。首先,系统让新的进程与旧的进程使用同一个代码段,因为他们的程序还是相同的,对于数据段和堆栈段,系统会复制一份给新的进程,这样父进程就将所有的数据都留给了子进程。子进程一旦运行,虽然它继承了父进程的一切数据,但实际数据已经分开,相互之间不在共享任何数据。
六:vfork()
创建一个新的PCB
父子进程公用相同的代码区和数据区
创建后子进程先于父进程执行
getpid():获取调用进程的ID
getppid():获取调用进程的父进程ID
七:wait(0):等待子进程结束;
八:信号中断处理
1. 信号的基本概念 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件)。
信号是硬件中断的软件模拟(软中断)。
每个信号用一个整型常量宏表示,以SIG开头,比如SIGCHLD、SIGINT等,也可以通过在shell提示符下键入kill –l查看信号列表,或者键入man signal查看更详细的说明。
信号的生成来自内核,让内核生成信号的请求来自如下三个方面:
用户:用户能够通过输入Ctrl+c、Ctrl+\,或者是终端驱动程序分配给信号控制字符的其他任何键来请求内核产生信号。
内核:当进程执行出错时,内核会给进程发送一个信号,例如非法段存取(内存访问违规)、浮点数溢出等。
进程:进程可以通过系统调用kill给另一个进程发送信号,进程也可以通过信号和另外一个进程进行通信。
接收默认处理:系统接收信号类型进行处理。
例如对当前正在执行的进程,用户按下Ctrl+c,将导致内核向进程发送一个SIGINT的信号,即终止进程。
忽略信号:进程可以通过代码忽略某个信号的处理,例如:signal(SIGINT, SIG_DFL);
但是某些信号是不能被忽略的。比如有两个信号既不能被忽略也不能被捕捉,它们是SIGKILL和SIGSTOP。
即进程接收到这两个信号后被终止进程。 捕捉信号并处理:进程可以事先注册信号处理函数,当接收到信号时,由信号处理函数自动捕捉并且处理信号。
2.
进程可以从以下几个方面使用信号:
注册一个特定信号捕捉函数,即指定进程的信号处理函数。
阻塞一个信号(即推迟信号的发生),比如处于一段临界代码,执行完临界代码后再启用这个信号。
向另外一个进程发送信号。 对软中断信号的处理分三种情况进行:
如果进程收到的软中断是一个已决定要忽略的信号,进程不做任何处理便立即返回。
进程收到软中断后便退出; 执行用户设置的软中断处理程序。
3.信号机制需要两个系统调用:
Signal():
通知内核如何处理某个特定信号(忽略、捕捉、默认处理)。
预置进程对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式:signal(sig,handler) signum:捕捉的信号类型,handler:对该信号进行捕捉的函数,该参数的取值范围有三种:
SIG_IGN:当进程运行中接收到指定信号时,忽略它,不做任何处理。 SIG_DFL:表示交由系统缺省处理。
列出func函数名:在该取值情况下,当进程接收到类型为sig的信号时,不管当前进程正在执行哪部分程序,都立即把控制权转给func函数。
当func完成后,进程的控制权将返回原进程中断处。
例:
main()
{ signal(SIGINT,SIG_IGN);
/* 设置忽略键盘中断信号*/
while(1) { printf("No answer to ‘Ctrl+c’ \n"); sleep(2); } }
4.kill():
用于发送指定类型的信号。
系统调用格式:int kill(pid,sig) pid:将要接收信号的进程ID;
sig是要发送的软中断信号。 函数说明:将参数sig指定的信号传递给pid标记的进程。
参数pid的取值分析如下: pid>0:表示一个进程ID号,核心将信号sig发送给进程号ID为pid的进程。
pid=0:表示同一进程组的进程,核心将信号sig发送给当前进程所属进程组的所有进程。
5.
main()
{char in[10]="hello";
while(1)
{ printf("Enter 'a' from keyboard if you want exit:\n"); scanf("%s",in); if(in[0]=='a')
/*当从键盘接收到首字母为“a”时,发送信号SIGQUIT终止 */
{ printf("The process will be killed by itself!\n"); kill(getpid(),SIGQUIT);
/*获取自身ID,发送信号SIGQUIT自我终止 */ } } }
九.中断练习:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int i;
void intfun(){
i=0;
printf("I am child:%d\n",getpid());
}
void main(){
int k,j,pid;
j=1;
while((pid=fork())==-1);
if(pid>0){
for(k=1;k<3;k++){
printf("how are you!\n");
printf("pid:%d\n",pid);
sleep(1);
}
kill(pid,17);//发送软中断信号给子进程
wait(0);//等待子进程终止
printf("Parent exited!\n");
exit(0);
}
else{
signal(17,intfun);
i=1;
while(i==1){
printf("I am child:%d\n",j++);
sleep(1);
}
printf("Child exited!\n");
exit(0);
}
}
子进程设置软中断信号17,父进程发送软中断信号给子进程,结束子进程的等待.
(2)子进程接收信号前处于等待阶段,一直执行while循环,接受信号后调用intfun方法,改变i的值,跳出循环.
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
int i;
void intfun(){
i=0;
printf("I am child:%d\n",getpid());
}
void main(){
int k,j,pid;
j=1;
while((pid=fork())==-1);
if(pid>0){
for(k=1;k<3;k++){
printf("how are you!\n");
printf("pid:%d\n",pid);
sleep(1);
}
kill(pid,17);//发送软中断信号给子进程
wait(0);//等待子进程终止
printf("Parent exited!\n");
exit(0);
}
else{
signal(17,intfun);
i=1;
while(i==1){
printf("I am child:%d\n",j++);
sleep(1);
}
printf("Child exited!\n");
exit(0);
}
}
l 1)运行编译后的程序,查看输出;按Ctrl+c键再查看程序执行结果。
l 2)将编译后的程序后台执行,查看输出;按Ctrl+c键再查看程序执行结果;在shell提示符下输入kill –INT pid,查看程序执行结果,其中,pid是该程序后台执行后显示的进程号。
3)将第一个signal(SIGINT,SIG_IGN)注释,看一下运行结果,分析原因。
忽略掉signal(SIGINT,SIG_IGN)之后,按ctrl+C之后所有的子进程都会中断,父进程调用stop函数结束等待.