程序自动维护工具make
1.
UNIX运行程序的生成过程
C语言是UNIX环境下最主要的程序设计语言。UNIX的C语言编译器cc能将C语言源程序、汇编程序和目标程序编译、链接成可执行文件。cc按文件后缀区分文件类别,常用的有:
l .c为后缀的文件表示C语言源程序
l .o为后缀的文件表示目标程序
l .a为后缀的文件表示由目标程序构成的档案库
cc首先调用cpp进行预处理,将源程序编译成以.o为后缀的目标文件,最后调用ld命令将目标文件和档案库文件链接成可执行文件:
2.
make的基本工作原理
make首先从指定的文件(缺省为makefile或Makefile)中获取可执行文件及程序模块间的相互关系的信息,然后根据各个文件的最后修改日期找出那些经过编译、但已经过时的模块(“过时”是指用来生成这些模块的源文件已经修改过,但这些模块本身却没有被更新),针对每一个过时的模块,按其相互关系中所给出的命令对该过时的模块进行更新操作。
3.
依赖关系描述
在make中,目标和生成该目标的源文件之间的关系称为依赖关系。一个目标的状态取决于它所依赖的那些对象的状态,一旦依赖对象的状态发生改变,make就执行依赖关系中所定义的那组命令来重新生成该目标。在makefile中,其语法格式为:
object:
source_list
[cmd_list]
make有自己的内部转换规则,其作用是描述如何将一类源文件转换成另一类文件(目标文件)。
4.
一个简单的make文件
# cat makefile
all: t_fork
t_signal
t_fork: t_fork.o
cc -o t_fork t_fork.o
t_signal:
t_signal.o
cc -o t_signal t_signal.o
.c.o:
cc -c $<
进程和信号
1.
进程和程序
进程是正在执行的一个程序的实例。
l 程序是静态概念,本身可以作为一种软件资源长期保存,进程是程序的执行过程,是动态概念,是动态地产生和消亡的;
l 进程不能脱离具体程序而虚设,程序规定了相应进程所要完成的动作;
l 进程由程序、数据集合和进程控制块组成。
2.
进程和子进程
子进程是由另外一个进程所产生的进程,产生子进程的进程为父进程。子进程继承父进程的环境。UNIX中创建子进程的方法是使用系统调用fork,fork复制了父进程的数据段、堆栈段和进程环境,子进程共享父进程的代码段。
3.
信号
信号是送到进程的“软中断”,信号通知进程在它们的环境中出现了非正常的事件。对信号的处理有3种:
l 忽略:停止输送信号给进程。
l 捕捉:用户提供对该信号的处理函数。
l 缺省:信号的缺省功能。
4.
信号的发送
l 系统送出:比如“浮点溢出”等。
l 键盘送出:比如在键盘上键入“CTRL_C”等。
l 进程送出:kill系统调用,包括命令行的调用和用户进程的调用。
5.
例子:
# cat t_fork.c
#include <stdio.h>
main ()
{
int pid;
register i,j;
printf ("main process
begin ...\n");
pid=fork ();
if (pid<0) {
perror ("fork
error!");
return (-1);
}
if (pid>0) /* parent
process */
printf ("\t\t\tin
parent process ...\n");
else /* child process */
printf ("in child
process ...\n");
for (i=0;i<10000;i++)
for
(j=0;j<10000;j++) ;
if (pid>0) /* parent
process */
printf ("\t\t\tparent
process over ...\n");
else /* child process */
printf ("child
process over ...\n");
exit (0);
}
# cat t_signal.c
#include <signal.h>
main ()
{
void k_fun ();
void i_fun ();
signal (SIGTERM,k_fun);
signal (SIGINT,i_fun);
while (1) ;
}
void k_fun ()
{
printf ("\nNOTE: I am
killed !\n");
exit (0);
}
void i_fun ()
{
printf ("\nNOTE: I am
interrupted !\n");
exit (0);
}
上机练习:
1. 系统接收屏幕输入,若30秒内不能成功接收,则提示超时,并退出程序。
2. 父进程进行10000*10000次加法运算,子进程负责计时(在屏幕上显示用去的秒数),父进程完成运算后,先退出子进程,父进程最后退出。
3. 在makefile中完成上述两个程序的编译、链接,并运行通过。
(相关函数:signal、fork、wait、kill、sleep)
共享内存通信
1.
UNIX系统进程间通信机制
消息、信号灯、共享内存是UNIX主机中各进程间常用的三种通信机制。
l 消息允许进程将格式化的数据流发到任何别的进程;
l 信号灯用来保持进程之间的同步运行;
l 共享内存允许多个进程共享其虚拟地址空间中的部分区域。
共享内存是UNIX系统上最快的通信方式。
另外还可以利用管道进行同步通信。
2.
利用共享内存进行进程间通信
# cat
t_shm_serv.c
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>
main ()
{
char
c;
int
shmid;
key_t
key;
void
*shmat ();
char
*shm, *s;
struct shmid_ds buf;
key= ftok ("my_shm_key",1);
if ((shmid=shmget
(key,27,IPC_CREAT|0666))<0) {
perror ("shmget error!");
exit (-1);
}
if ((shm=shmat (shmid,NULL,0))==NULL) {
perror ("shmat error!");
exit (-1);
}
s=shm;
for (c='a';c<='z';c++)
*s++=c;
*s=0;
printf ("waiting for client ...");
fflush (stdout);
while (*shm!='*')
sleep (1);
printf ("\nclient
arrived !\n");
shmdt (shm);
shmctl (shmid,IPC_RMID,&buf);
exit (0);
}
# cat
t_shm_cli.c
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/ipc.h>
#include
<sys/shm.h>
main ()
{
char
c;
int
shmid;
key_t
key;
char
*shm,*s;
key=ftok ("my_shm_key",1);
if ((shmid=shmget (key,27,0666))<0) {
perror ("shmget error!\n");
exit (-1);
}
if ((shm=shmat (shmid,NULL,0))==NULL) {
perror ("shmat error!\n");
exit (-1);
}
for (s=shm;*s!=0;s++)
printf ("%c",*s);
printf ("\n");
*shm='*';
shmdt (shm);
exit (0);
}
3.
利用信号灯进行同步
# cat t_sem_w.c
#include
<stdio.h>
#include
<sys/types.h>
#include
<sys/ipc.h>
#include
<sys/sem.h>
main ()
{
struct sembuf sem1_p = { 0, -1, 0 };
struct sembuf sem2_v = { 1, 1, 0 };
int
i,sem_id,sem_val;
key_t
_ipc_key;
int
val;
union semum {
int val;
struct semid_ds *buf;
ushort *arry;
} semctl_arg;
_ipc_key=ftok ("my_sem_key",1);
sem_id=semget (_ipc_key,2,0666|IPC_CREAT);
semctl_arg.val=1;
semctl (sem_id,0,SETVAL,semctl_arg);
semctl_arg.val=0;
semctl (sem_id,1,SETVAL,semctl_arg);
for (i=1;i<=10;i++) {
semop (sem_id, &sem1_p, 1 );
printf ("This [%d]",i);
fflush (stdout);
semop (sem_id, &sem2_v, 1 );
}
semctl (sem_id,0,IPC_RMID,0);
return (0);
}
# cat t_sem_r.c
#include
<sys/types.h>
#include
<sys/ipc.h>
#include
<sys/sem.h>
main ()
{
struct sembuf sem1_v = { 0, 1, 0 };
struct sembuf sem2_p = { 1, -1, 0 };
int
i,sem_id,sem_val;
key_t
_ipc_key;
int
val;
union semum {
int val;
struct semid_ds *buf;
ushort *arry;
} semctl_arg;
char t_buf[50];
_ipc_key=ftok ("my_sem_key",1);
sem_id=semget (_ipc_key,2,0666|IPC_EXCL);
for (i='a';i<='j';i++) {
semop (sem_id, &sem2_p, 1 );
memset (t_buf,0,sizeof t_buf);
sprintf (t_buf,"echo \" This [%c]\"
>/dev/ttyp0",i);
system (t_buf);
sleep (1);
semop (sem_id, &sem1_v, 1 );
}
return (0);
}
上机练习:
1. 从一个进程通过共享内存发送若干字符串给第二个进程,第二个进程每接收一个就在屏幕上显示一次,直到接收到“-o-”为止。
2. 考虑共享内存中最多可以容纳5个字符串的情况。
socket与网络通信
1.
socket通信机制
socket(套接字)提供了进程间通信的一般方法,并允许使用复杂的通信协议。其核心结构包括三部分:套接字层、协议层、设备层。
Socket支持UNIX系统域,在socket()系统调用中用关键字AF_UNIX表示,socket也支持互连网域,用关键字AF_INET表示。
常用的socket有两种类型,虚电路和数据报,其关键字分别为SOCK_STREAM和SOCK_DGRAM。虚电路(又称流)提供双向、可靠、有序的通信,而数据报不能保证顺序传递信息,而且可能丢失信息。
2.
配置文件
/etc/hosts
3.
一个简单的例子
# cat
t_tcp_serv.c
#include
<stdio.h>
#include
<signal.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<netdb.h>
main ()
{
int cc,len,sd,sdc,sum;
char hostname[21];
struct hostent *host;
struct sockaddr_in sock;
char buf[100];
sd=socket (AF_INET,SOCK_STREAM,0);
if (sd<0)
perror ("socket error!");
gethostname (hostname,20);
host=gethostbyname (hostname);
if (host==NULL)
perror ("gethostbyname error !");
sock.sin_family=AF_INET;
memcpy ((char
*)&sock.sin_addr,host->h_addr,host->h_length);
sock.sin_port=htons (7500);
if (bind (sd,(struct sockaddr
*)&sock,sizeof (sock))<0) {
perror ("bind error !");
close (sd);
exit (0);
}
printf ("Port# %d\n", ntohs
(sock.sin_port));
printf ("... waiting for connection
...\n");
if (listen (sd,1)<0) {
perror ("listen error !");
close (sd);
exit (0);
}
len=sizeof (sock);
sdc=accept (sd,(struct sockaddr *)&sock,&len);
if (sdc<0) {
perror ("accept error !");
close (sd);
exit (0);
}
printf ("Connected to client !\n");
while (1) {
if (cc=recv (sdc,buf,sizeof (buf)-1,0)) {
buf[cc]=0;
printf ("%s",buf);
fflush (stdout);
}
else if (cc==0) {
printf ("Received over !\n");
close (sdc);
close (sd);
printf ("... server disconnected
...\n");
exit (0);
}
else {
perror ("receive error !\n");
exit (-1);
}
}
}
# cat t_tcp_cli.c
#include
<stdio.h>
#include
<signal.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<netdb.h>
main ()
{
int cc,len,sd,sdc,sum;
char hostname[21];
struct hostent *host;
struct sockaddr_in sock;
char buf[100];
sd=socket (AF_INET,SOCK_STREAM,0);
if (sd<0) {
perror ("socket error !");
exit (0);
}
strcpy (hostname,"opentp");
host=gethostbyname (hostname);
sock.sin_family=AF_INET;
memcpy ((char
*)&sock.sin_addr,host->h_addr,host->h_length);
sock.sin_port=htons (7500);
if (connect (sd,(struct sockaddr
*)&sock,sizeof (sock))<0) {
perror ("connect error !");
close (sd);
exit (0);
}
printf ("connect to server [%s]
...\n",hostname);
printf ("Type in messages: (CTRL-D to
exit)\n");
while (1) {
memset (buf,0,sizeof buf);
cc=read (0,buf,sizeof (buf)-1);
if (send (sd,buf,cc,0)<0)
printf ("Send [%s] error
!\n",buf);
if (cc==0)
break;
}
close (sd);
printf ("... client disconnected ...\n");
exit (0);
}
上机练习:
1. 从一台UNIX服务器向另一台UNIX服务器发送信息,接收到后发送响应给发送方。
2. 从一台UNIX服务器向另一台UNIX服务器发送一个文件。