进程&进程间通信


1.进程的创建

fork调用

main()

{

pid_t pid;

printf(“ Now only one  process\n” );

printf(“ Calling fork… \n” );

pid=fork();

if (!pid)

printf(“ I’ m the  child\n” );

else if (pid>0)

printf(“ I’ m theparent, child has pid %d\n” ,pid);

else

print (“ Fork fail!\n” );

}

 

exec调用

如果 fork() 是程序员唯一可使用的建立进程的手段,那么 Linux 的性能会受很大影响。

因为fork() 只能建立相同程序的副本。幸运的是,Linux 还提供了系统调用 exec 系列,它可以用于新程序的运行。exec 系列中的系统调用都完成相同的功能,它们把一个新程序装入调用进程的内存空间,来改变调用进程的执行代码,从而形成新进程。如果 exec 调用成功,

调用进程将被覆盖,然后从新程序的入口开始执行。这样就产生了一个新的进程,但是它的进程标识符与调用进程相同。这就是说,exec没有建立一个与调用进程并发的新进程,而是用新进程取代了原来的进程。所以,对 exec 调用成功后, 没有任何数据返回, 这与 fork()不同。


execl( “ /bin/ ls” , ” ls” , ” -l ” ,NULL);


main()

{

int pid;

/* fork 子进程*/

pid=fork();

switch( pid) {

case -1:

perror("fork failed");

exit(1);

case 0:

execl("/bin/ls","ls","-l","--color",NULL);

perror(" execlfailed");

exit(1);

default:

wait(NULL);

printf(" ls  completed\n");

exit(0);

}

}

在程序中,在调用 fork() 建立一个子进程之后,马上调用了 wait(),使父进程在子进程结束之前,一直处于睡眠状态。所以,wait()向程序员提供了一种实现进程之间同步的简单方法。

 

2.进程标识符

系统把标识符 0 和 1 保留给系统的两个重要进程。进程0 是调度进程,

它按一定的原则把处理机分配给进程使用。进程 1 是初始化进程,它是程序/sbin /init 的执

行。进程 1 UNIX系统那其它进程的祖先,并且是进程结构的最终控制者

利用系统调用 getpid 可以得到程序本身的进程标识符,其用法如下:

pid=getpid();

利用系统调用 getppid 可以得到调用进程的父进程的标识符,其用法如下:

ppid=getppid();

 

3.进程优先级

进程的优先级

系统以整型变量 nice 为基础,来决定一个特定进程可得到的 CPU 时间的比例。nice 之

值从 0 至其最大值。我们把 nice 值称为进程的优先数。进程的优先数越大,其优先权就越

低。普通进程可以使用系统调用 nice()来降低它的优先权,以把更多的资源分给其它进程。

具体的做法是给系统调用 nice 的参数定一个正数,nice()调用将其加到当前的 nice 值上。

例如:

#include <unistd.h>

nice(5);

这就使当前的优先数增加了 5,显然,其对应进程的优先权降低了。

超级用户可以用系统调用 nice()增加优先权,这时只需给 nice ()一个负值的参数,如:

nice(-1);

 

4. 守护进程

守护进程是一种后台运行并且独立于所有终端控制之外的进程。

守护进程与后台运行程序是有区别的,加&启动的程序是后台运行的程序,但它仍然拥有控制终端

 

基本上任何一个程序都可以后台运行,但守护进程是具有特殊要求的程序,比如要脱离自己的父进程,成为自己的会话组长等,这些要在代码中显式地写出

换句话说,守护进程肯定是后台进程,但反之不成立。守护进程顾名思义,主要用于一些长期运行,守护着自己的职责(监听端口,监听服务等)。我们的系统下就有很多守护进程。


守护进程的启动

要启动一个守护进程,可以采取以下的几种方式:

1.在系统期间通过系统的初始化脚本启动守护进程。这些脚本通常在目录 etc/rc.d

通过它们所启动的守护进程具有超级用户的权限。系统的一些基本服务程序通常都是通过

这种方式启动的。

2.很多网络服务程序是由 inetd 守护程序启动的。在后面的章节中我们还会讲到它。

它监听各种网络请求,如 telnet、 ftp 等,在请求到达时启动相应的服务器程序 (telnet server 、ftp server 等)。

3.由 cron 定时启动的处理程序。这些程序在运行时实际上也是一个守护进程。

4.由 at 启动的处理程序。

5.守护程序也可以从终端启动,通常这种方式只用于守护进程的测试,或者是重起因某种原因而停止的进程。

 

5. 进程间通信

5.1信号

信号不但能从内核发往一个进程,也能从一个进程发往另一个进程。用 kill 命令SIGTERM

信号发送给这个进程,SIGTERM 将终止此进程的执行。

常用的信号和它们的意义:

l SIGHUP

终止一个终端时,内核就把这一种信号发送给该终端所控制的所有进程。

SIGINT

当一个用户按了中断键(一般为 Ctrl+C后,内核就向与该终端有关联的所有进程发送这种信号。

SIGTERM

这种信号是由系统提供给普通程序使用的,按照规定,它被用来终止一个进程

SIGALRM

当一个定时器到时的时候,内核就向进程发送这个信号。定时器是由改进程自己用系统调用 alarm()设定的。

 

信号 SIGQUIT、SIGILL、SIGTRAP、SIGSYS 和 SIGFPE 会导致一个非正常终止,它们将发生核心转贮,即把进程的内存映象写入进程当前目录的 core文件之中。

 

命令运行时使用CTRL+C,强制终止当前进程

命令运行时使用CTRL+Z,强制当前进程转为后台,并使之挂起(暂停).

(1)CTRL+Z挂起进程并放入后台
(2) jobs 显示当前暂停的进程
(3) bg %N 使第N个任务在后台运行(%前有空格)
(4) fg %N 使第N个任务在前台运行
默认bg,fg不带%N时表示对最后一个进程操作!

 

信号的处理:

intsignal (int sig, __sighandler_t  handler);

第一个参数 sig 指明了所要处理的信号类型

第二个参数可以有三个取值:

01.  一个返回值为整数的函数地址。

02.SIG_IGN 这个符号表示忽略信号

03. SIG_DFL这个符号表示恢复系统对信号的默认处理。

 

5.2管道

道就是将一个程序的输出和另外一个程序的输入连接起来的单向通道

当进程创建一个管道的时候,系统内核同时为该进程设立了一对文件句柄(一个流),

一个用来从该管道获取数据(read),另一个则用来做向管道的输出(write

 

有名管道(FIFOs ) 有名管道和管道的操作是相同的,只是要注意,在引用已经存在的有名管道时,首先要用系统中的文件函数来打开它的操作。

#mkfifo –m 0666sampleFIFO

以上的两个命令是等价的,它们都会在当前的文件系统中建立一个名字为 samlpeFIFO的有名管道。

 

6. System V IPC P90

UNIXSystem V 中引入了几种新的进程通讯方式,即消息队列(Message

Queues,信号量(semaphores)和共享内存(shared memory套接字。统称为 System V IPC

(1)信号量,用来管理对共享资源的访问

(2)共享内存,用来高效地实现进程间的数据共享

(3)消息队列,用来实现进程间数据的传递

 

关键字

System V IPC 的一个显著的特点,是它的具体实例在内核中是以对象的形式出现的,我们称之为 IPC 对象。每 IPC对象在系统内核中都有一个唯一的标识符

标识符只在内核中使用,IPC 对象在程序中是通过关键字(key)来访问

而且,要访问同一个 IPC 对象,Server Client必须使用同一个关键字

Ftok生成关键字

key_t mykey;

mykey = ftok(".", 'a');

mykey = ftok("/tmp/myapp", 'a’);

ipc命令

ipcs 命令在终端显示系统内核的 IPC 对象状况。

ipcs –q  只显示消息队列

ipcs –m  只显示共享内存

ipcs –s  只显示信号量

 

使用 ipcrm 命令强制系统删除已存在的 IPC 对象。

 

6.1消息队列 P92

 

通过设定 mtype 值,我们可以进行单个消息队列的多向通讯。如下图,client 可以给它

向 server 发送的信息赋于一个特定的 mtype 值,而 server 向 client 的信息则用另一个 mtype值来标志。这样,通过 mtype 值就可以区分这两向不同的数据

 

消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。

 

消息队列与管道的区别:最主要的区别是管道通信是要求两个进程之间要有亲缘关系,只能承载无格式的字节流,而消息队列当中,通信的两个进程之间可以是完全无关的进程,它是有格式的(可类比TCP\UDP)。至于它与有名管道的区别,首先FIFO是要存储在磁盘上的一种通信方式,而消息队列是在内存中的。

 

Linux的消息队列(queue)实质上是一个链表,它有消息队列标识符(queue ID).

msgget创建一个新队列或打开一个存在的队列;

msgsnd队列末端添加一条新消息;

msgrcv从队列中取消息,取消息是不一定遵循先进先出的,也可以按消息的类型字段取消息. 

 

6.2共享内存

mmap其相关系统调用

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read()write()等操作。

注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作

 

共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以1.通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以2.通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间

 

6.3共享内存和消息队列

消息队列,FIFO,管道的消息传递方式一般为
    1
:服务器得到输入
    2
:通过管道,消息队列写入数据,通常需要从进程拷贝到内核。
    3
:客户从内核拷贝到进程
    4
:然后再从进程中拷贝到输出文件
   
上述过程通常要经过4次拷贝,才能完成文件的传递

与管道一样,每个数据块有一个最大长度的限制,并且系统中所有队列所包含的全部数据块的总长度也有一个上限

  而共享内存只需要
    1:
从输入文件到共享内存区
    2:
从共享内存区输出到文件
   
上述过程不涉及到内核的拷贝,所以花的时间较少。

本站公众号
   欢迎关注本站公众号,获取更多程序园信息
开发小院