进程是针对系统而不是用户的,对于用户而言,其面对的是程序,当用户输入指令执行一个程序的时候,对系统而言它将启动一个进程,但是和系统不同的是,在这个进程中,系统可能需要再启动一个或者多个进程来完成独立的多个任务,多进程编程主要包括进程控制和进程间通信。
linux环境下进程结构:
linux进程在内存中有三部分数据:代码段 数据段和堆栈段,在CPU都有上述三种段寄存器,以方便操作系统运行
代码段:存放程序源代码,多个进程运行相同的程序,他们就使用相同的代码段
堆栈段:存放子程序的返回地址 子程序的参数以及程序的局部变量
数据段:存放程序的全局变量 常数以及动态数据分配的数据空间
linux下的多进程编程包括进程控制和进程间通信,下面详细介绍:
linxu下的进程控制:
传统的linux环境下,有两个基本的操作用于创建和修改进程:fork()函数用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;exec()函数族用来启动另外的进程代替当前的进程。
fork()函数
例子:
void main(){
for(;;)
fork();
}
该进程也不做就是一直循环的fork,结果是不断的产生进程,而这些进程又不断产生新的进程,系统满的时候就瘫痪了。所以系统管理员可以预先指定每个用户最多运行的最大进程数,只要运行者不是root权限就没问题。
exec()函数族
该函数族用来在一个进程中启动另一个程序的执行,一个进程一旦调用了exec函数族,它本身就死亡了,系统把原来进程的代码段替换成程序的代码,废弃原有的数据段和堆栈段,使用新程序的数据段和堆栈段,唯一留下的就是进程号,对于系统来说,还是同一个进程,但是已经是另一个程序了。
如果程序想启动一个新的程序但是不结束自己,那样的话就得用fork和exec函数族配合使用了。
linux下的进程间通信:
首先进程间通信至少可以通过传送文件来实现,不同的进程通过一个或者多个文件来传递信息,在很多应用程序中都使用了这个方法,不过这种方式比较低级,很简单!
linux系统中实现进程间通信的方式很多,常用的有:管道 消息队列 共享内存 信号量 套接口等。
管道:
管道是进程间通信的最古老的方式,分两种:有名管道和无名管道。父进程和子进程间通信使用无名管道,同一机器上的任意两个进程间的通信使用有名管道。
无名管道:
无名管道由函数pipe()来创建 int pipe(int filedis[2]) 参数filedis返回两个文件的描述符号:filedis[0]为读而打开 filedis[1]为写而打开 filedis[1]的输出是filedis[0]的输入
例子:
#define INPUT 0
#define OUTPUT 1
void main(){
int file_descri[2];
pid_t pid;
char buf[256];
int return_count;
pipe(file_descri);//创建了无名管道
pid=fork();
if(-1==pid){
printf("ERROE in fork\n");
exit(1)
}
else if(0==pid){
printf("child process\n");
close(file_descri[INPUT]);
write(file_descri[OUTPUT],"www.jiaojiaoni.com",strlen("www.jiaojiaoni.com"));
exit(0);
}
else{
printf("Parent process!\n");
close(file_descri[OUTPUT]);
return_count=read(file_descri[INPUT],buf,sizeof[buf]);
printf("%d bytes of data,the data is %s \n",return_count,buf);
}
}
有名管道
(有名管道就是有名字的管道):
linux系统下的有名管道由两种方式创建:命令行方式mknod和函数mkfifo
方式一: mkfifo("myfifo","rw")
方式二: mknod myfifo p
上述两种方式都在当前目录下生成了一个名为fifo的有名管道。
生成了有名管道后就可以使用一般的文件io函数如open close read和writre对其进行操作了
例子:(假如我们已经有了一个名为myfifo的有名管道)
//进程一 读有名管道
void main(){
FILE* infile;
int count=1;
char buf[80];
infile=fopen("myfifo","r");
if(NULL==infile){
printf("ERROR in fopen!\n");
exit(1);
}
count=fread(buf,1,80,infile);
while(count>0)
printf("receive from pipe:%s\n",buf);
fclose(infile1);
}
//进程二 写有名管道
void main(){
FILE* outfile;
int count=1;
char buf[80];
outfile=fopen("myfifo","w");
if(NULL==out_file){
printf("ERROR open!\n");
exit(1);
}
sprintf(buf,"hello everyone my website is www.jiaojiaoni.com\n");
fwrite(buf,1,80,outfile);
fclose(outfile);
}
共享内存
共享内存是运行在同一机器上的进程间的通信最快的方式,因为数据直接存在内存不需要在不同的进程间进行复制,通常由一个进程创建一个共享内存区,其余进程对这块内存进行读写。
得到共享内存有两种方式:映射/dev/mem设备和内存映像文件
映射/dev/mem不给系统带来额外的开销,但是在实际中应用不常用,因为它控制存取的是实际的物理内存,在linux系统下,只有通过限制linux系统存取的内存才可以做到
通常常用的方式是使用shmXXX函数族来实现共享内存进行存储
int shmget(key_t key,int size,int flag) 获得一个共享存储标识符,也就是请求分配size大小的内存用作共享内存 key_t关键字用来存储引用标识符,是个长整型数据,在头文件sys/types.h中定义
上述函数执行完毕,共享内存就创建完毕了,其余进程使用 shmat()函数就可以连接到自身的地址空间中了
void* shmat(int shmid,void* addr,int flag)
shmid为shmget函数返回的共享存储标识符
addr和flag决定了以什么方式来确定连接地址
函数的返回值就是该进程数据段所连接的实际地址,也就是共享内存的地址,进程可以对其进行读写操作
使用共享存储来实现进程间的通信的注意点是对数据存取的同步,必须确保一个进程去读取数据时,它所想要的数据已经写好了,通常信号量可以被用来实现对共享存储数据存取的同步
信号量从本质上说是一个计数器,用来计算对某个资源的存取情况,在这里可以用于同步的实现。
套接字
套接字是实现linux操作系统和其他操作系统之间的进程间通信的方式之一,www ftp telnet等服务都是基于套接字的
除了适用于异地的计算机进程间通信外,套接字同样适用于本地同一计算机内部的进程间通信。