第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<sys/types.h>
#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)    收藏  举报

导航