第3课 进程与进程间通信
3.1 telnet
telnet命令用于远程登录主机,对主机进行管理。接下来需要实现的是,在SecureCRT中通过telnet命令登录开发板。
首先,需要对uClinux的“Network Applications”进行配置,进入/work/uClinux-dist路径下执行sudo make xconfig,弹出
点击“Target Platform Selection”以后,进入设置界面,在第一节课的基础上,按下图选择,
选择完成后,点击“Main Menu”->”Save and Exit”。弹出如下界面,按照下面的顺序依次选择,注意第二步中,是把telnetd改为y而不是telnet。选择完后,同样点击“Main Menu”->”Save and Exit”,等待程序设置完成。
在Ubuntu里设置完成后,还需将它下载到开发板才行,因为最终的目的是要在开发板里面跑起来。
然后,在第二节课的基础上编译一下应用程序:
进入/work/uClinux-dist目录下,执行以下语句,仅编译应用程序。
其次安装文件到/work/uClinux-dist/romfs,执行下语句。
为应用程序创建一个镜像。
编译完成后,将程序烧到开发板,具体的硬件连接,以及网络的设置参照第2课的2.3小节程序下载。
最后,下载完成后,掉电重启,
接下来,测试telnet是不是能够进行正常工作。
在SecureCRT中,删除之前的串口连接,并新增一个telnet连接,按照下图设置,因为开发板的IP是“192.168.0.1”所以在“Hostname”里填入对应的地址。
点击连接,转到bin目录下,运行./hello,打印出了“Hello,uClinux!”,表示telnet设置正确。和第2课的串口比起来,在程序运行效果上没有什么区别,如果需要远程调试的话,telnet就会比串口更有优势。
3.2 创建进程
创建进程有两个函数fork()和vfork()函数。
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程(child process)。所以调用fork()函数以后,有两个进程在执行,一个是父进程(parent process),一个是子进程。这两个进程在不同的命名空间,也就是说相同的变量在这两个进程里执行的运算互不影响。
接下来看一些例子,在/work/uClinux-dist/user/scu目录下,新建testfork1.c文件。
该文件的代码为:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if(pid<0)
printf("error in fork!\n");
else if(pid == 0)
printf("I am the child process,ID is %d\n",getpid());
else
printf("I am the parent process,ID is %d\n",getpid());
return 0;
}
保存以后,运行程序,
可以看出,有两个进程,父进程的进程号为4622,而子进程的进程号为4623。
再新建一个testfork2.c文件,代码为:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if(pid<0)
printf("error in fork!\n");
else if(pid == 0)
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the child process,ID is %d\n",getpid());
}
else
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the parent process,ID is %d\n",getpid());
}
return 0;
}
运行程序,如下,
变量cnt在两个进程里的值都是1,说明这两个进程互不影响,各做各的。
对于vfork()函数,它也是创建进程,它与fork()函数的区别是:
1)父进程和子进程分享共同的变量。
2)只有子进程发出exit命令或者执行exec函数父进程才会执行。
3)在uClinux下,只支持vfork()函数。
在/work/uClinux-dist/user/scu目录下,新建testvfork1.c文件。代码为:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid<0)
printf("error in fork!\n");
else if(pid == 0)
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the child process,ID is %d\n",getpid());
}
else
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the parent process,ID is %d\n",getpid());
}
return 0;
}
保存,运行:
新建testvfork2.c文件,代码为:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid<0)
printf("error in fork!\n");
else if(pid == 0)
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the child process,ID is %d\n",getpid());
_exit(0);
}
else
{
cnt++;
printf("cnt=%d\n",cnt);
printf("I am the parent process,ID is %d\n",getpid());
}
return 0;
}
保存执行程序:
有了exit命令以后,程序在运行到exit就会结束子进程,然后执行父进程。
再新建一个pratice.c文件,代码为:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
fork();
fork() && fork() || fork();
fork();
printf("+");
}
保存运行,输出的“+”的个数有20个,如下图所示:
3.3 信号
信号是进程间通信的一种机制,接收到该信号的进程通过软中断的方式来响应这个信号,触发一些事先指定或特定的事件。函数原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
其中,func是一个函数指针,指向了一个带有1个int类型参数,且返回值为空的函数,signal的作用就是当收到sig信号时,进程就会去执行func指向的函数。另外func也可以用下面三个特殊值之一来代替:
SIG_IGN 忽略信号
SIG_DFL 恢复默认
SIG_ERR 错误发生
一些例子,在scu目录下新建一个testsignal1.c文件。代码
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("\nOUCH! - I got signal %d.", sig);
}
int main()
{
(void)signal(SIGINT, ouch);
while(1)
{
printf("\nHello, Linux!");
sleep(1);
}
}
程序一直在循环打印“Hello,Linux!”,但当按下“CTRL+C”以后,进程收到信号,转去执行ouch()信号处理函数,所以打印出了“OUCH!-I got signal 2.”,如果不在给“CTRL+C”信号,进程又继续打印“Hello,Linux!”。当然,要停止该程序,只有关闭终端。
进程可以通过调用kill()函数向包括它本身在内的其他进程发送一个信号。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid表示要发送信号到达的目标进程的进程id,sig为发送的信号值。成功时,返回0。失败时,返回-1,并设置 errno变量,
errno = EINVAL 给定的信号无效
errno = EPERM 发送进程权限不够
errno = ESRCH 目标进程不存在
Linux信号机制向我们提供了一个有用的闹钟功能,进程可以通过调用alarm函数在经过预定时间后发送一个SIGALRM超时警告信号:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
新建一个testalarm.c文件,代码为:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("Alarm has gone off.\n");
}
int main()
{
printf("Alarm starting...\n");
alarm(5);
printf("Waiting for alarm to go off.\n");
(void)signal(SIGALRM, ouch);
pause();
printf("Done.\n");
exit(0);
}
保存,运行
进程等待五秒以后,发送一个SIGALRM信号,转去执行ouch()函数。
3.4 管道
管道只能进行单向的数据传输,FIFO先入先出的方式,一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
有两种类型:匿名管道与命名管道,匿名管道的缺点只能在有亲缘关系的进程间进行通信,比如父子进程间。而在非亲缘关系间就不行。命名管道就不一样。
主要介绍命名管道,用mkfifo() 或者mknod()来创建命名管道。
pipeserver和pipeclient两个进程间通过命名管道通信的例子:
在user目录下创建两个文件pipeserver.c和pipeclient.c文件。并加入相应的代码:
Pipeserver.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define PIPE_NAME "/var/tmp/autopipe"
static void ouch(int sig)
{
unlink(PIPE_NAME);
exit(0);
}
int main(void)
{
int pipefd;
int ret;
char cmdline[256];
ret = mknod( PIPE_NAME, S_IFIFO|0x1FF, 0 );
if( ret == -1 )
exit(ret);
(void)signal(SIGINT, ouch);
pipefd = open( PIPE_NAME, O_RDONLY, 0 );
if( pipefd == -1 )
exit(pipefd);
for(;;)
{
ret = read( pipefd, cmdline, sizeof(cmdline) );
if( ret < 0 )
{
printf("\nError in reading pipefd!");
exit(-1);
}
if( ret == 0 )
continue;
printf("\n[cmdline=%s]",cmdline);
}
close( pipefd );
exit(0);
}
Pipeclient.c文件代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define PIPE_NAME "/var/tmp/autopipe"
int main(int argc, char *argv[])
{
int pipefd;
int ret;
char cmdline[256];
pipefd = open( PIPE_NAME, O_WRONLY, 0 );
if( pipefd == -1 )
exit(pipefd);
write( pipefd, argv[1], strlen(argv[1])+1 );
close( pipefd )
exit(0);
}
运行pipeserver,另外开一个terminal,运行pipeclient,如下图
从中可以看出,在pipeclient里面输入的字符,在pipeserver里面可以读出来,但要延迟一次输入,也就是上一次输入的下一次才能读出来。
3.5 总结
这节课主要讲了telnet和进程以及进程之间的通信方式,telnet用于远程登录主机,在远距离方面telnet比串口要更具优势。进程方面,fork()和vfork()函数用于创建进程,有相似之处也有不同;进程之间的通信主要介绍了信号和管道两种方式,并配了相应的程序来练习,加深了对进程的理解。
posted on 2018-03-25 20:42 Liu_Farrell 阅读(1353) 评论(0) 收藏 举报