进程一二事

一、进程的模式

一个进程是一个正在运行的程序实体,包括程序计数器,寄存器与变量的当前值。
下面是单核cpu同时执行4个进程时的示意图
在这里插入图片描述
要注意在进程来回切换的过程中,每个进程的所占用的时间以及重新调度的时间并不是一定的,因此在编写多进程的程序时,进程调度之间,不能做好关于时间序列的内置假设。

进程与程序的区别

进程(process)与程序(program)之间的区别,既微妙又非常重要。
诚如上面所说,进程是程序的一个运行实例,程序是一个静态的概念,而进程是一个动态的概念。
用一个厨师按照菜谱做菜的比喻能够比较生动的说明进程与程序之间的区别:
厨师可以看成是cpu,菜与调料可以认为是输入的数据,菜谱就是程序,而厨师按照菜谱,加入各种材料去烹饪的过程就是进程。
进程拥有程序,输入,输出以及状态。

二、进程创建

进程的创建方法一般有四种:
1.操作系统启动时创建的进程
2.由一个运行的进程创建另一个进程
3.由用户创建的进程
4.批处理程序创建

2.1 unix与windows创建进程的方法

2.1.1 unix创建进程的方法

在unix中能够创建进程的系统调用为fork函数,这个系统调用将会创建一个与调用进程完全一致的新进程,包括同样的内存映射,环境变量与文件句柄。
注意pid=fork(),对于子进程来说,返回的pid为0,父进程返回的pid是子进程真正的pid。
只有当子进程调用execve函数后,子进程才会修改自己的内存映射执行新的程序。

2.1.2 windows创建进程的方法

在windows中创建进程的方法是调用win32函数CreateProcess,这个函数不但会创建一个新的进程而且会装载对应的程序,相当于unix先调用fork程序,然后调用execve程序。

2.2 unix与windows创建进程后地址空间处理的异同

必须要注意的是,当一个新的进程被创建后,子进程与父进程将会拥有独立的地址空间,任何一个进程的对自己地址空间的修改都不会影响到另一个。

windows与unix子进程对父进程创建的堆空间的处理:
对于父进程创建的堆空间(即通过new出来的空间),子进程对这个堆空间的处理方式,unix与windows是不相同的。
在windows中,子进程将会开辟一块与父进程创建的堆空间大小一致的空间,并拷贝相应的值;
在unix中,子进程开始时会与父进程共享这块堆空间,直到有任何一方尝试去修改这块堆空间的地址。

三、进程终止

进程终止的方式可分为四种情况:
1.正常终止(自愿)
2.错误退出(自愿)
3.程序崩溃(非自愿)
4.被其他进程终止(非自愿)

正常终止:程序执行完应有的功能后退出。
错误退出:程序执行时发现某些条件不满足,然后退出。
程序崩溃:因为程序的某些bug而导致的非正常退出,比如执行非法指令,地址越界,由内核程序强制退出。
被其他进程终止:由另一个进程调用系统调用来告诉操作系统杀掉某个进程。在unix种使用kill函数,在windows中使用TerminateProcess函数。需要注意的是:终止其他进程的进程,必须具备相应的权限才能够执行kill或TerminateProcess。

四、进程层级

4.1 unix与windows进程层级的区别

4.1.1 unix中的进程层级

在unix中父进程与子进程以及更后辈的进程会组合成一个进程树,比如用户用键盘发送一个信号,这个信号将会传递到所有与当前窗口相关联的进程。
unix中可以通过pstree查看进程树的结构。

4.1.2 windows中的进程层级

在windows中并没有进程层级的概念,所有的进程都是平等的。唯一有点体现进程层级的概念就是当一个进程被创建后,父进程可以拥有其句柄。
windows中可以通过tasklist命令,查看进程列表。

五、进程状态

进程拥有3种状态:
1.running(正在占据着cpu运行)
2.ready(可以运行;等待cpu资源)
3.blocked(不可运行,等待外部资源满足)
进程转台切换

六、进程切换的实现

6.1进程表

操作系统维护进程表来支持进程的切换,进程表包含了每一个进程运行的必须信息。
进程的运行信息主要包含进行管理信息(PCB),内存管理信息与文件管理信息。
在这里插入图片描述
当进程状态从running切换到ready或者blocked时,进程的相关信息将会被填入进程表中。

6.2进程切换过程

6.2.1 进程切换类型

导致进程切换的原因大致可分为两大类——进程主动切换与进程被动切换。
进程主动切换一般发生于进程需要等待外部资源或者主动调用sleep(),即进程的状态从running->block。
进程被动切换一般发生于进程时间片用完,被其他高优先级进程抢占,以及触发中断,即进程的状态从running->ready。

6.2.2主动切换与被动切换的过程

6.2.2.1进程主动切换的过程

进程在正常运行中一般处于用户态,而用户态并不能执行进程切换,只有在内核态下才能实现相关功能。
因此进程主动切换的相关步骤如下:
1.保存进程相关信息到进程表中;
2.执行相应的系统调用,从用户态切换到内核态;
3.在内核态下执行进程调度相关程序,选择下一个可执行进程
4.加载目标进程

6.2.2.2进程被动切换的过程

进程被动切换是在进程执行的过程中,被外部中断影响,导致程序切换,已下是进程被动切换的具体步骤:
在这里插入图片描述

七、进程并发模型

进程并发能够在一定程度上提高cpu的利用率,以下列模型来粗略计算下cpu的使用率。
假设每个进程的io等待时间占据整体运行时间的比例为p,内存一次性能够容纳n个进程,那么cpu的使用率则为:
CPU utilization = 1 − p^n。
在这里插入图片描述
从上图可以看出,当进程的io等待占比越大,那么内存中容纳越多的进程,越能提高cpu的使用率。

如果一个8G的内存,其中2G是操作系统占用的空间,其余每个进程的需要2G的内存空间,Io等待占比为0.8,那么在内存满载的情况下,cpu的使用率就是1-0.8^3
=0.488;我们再增加8G的内存,此时内存可以容纳7个进程,cpu的使用率变为1-0.8^7=0.79,cpu的使用率提升了大概30%。如果再增加8G的内存,cpu的使用率大概为91%,提升了12%。
可以看出随着内存的增加,cpu使用率的提升幅度会逐渐衰减。
大家可以据此粗略地估计出增加多少内存能够有最高的性价比。

八、进程间的通信

8.1进程间通信的作用

1.数据传输
一个进程需要将它的数据发送给另一个进程;
2.资源共享
多个进程之间共享同样的资源;
3.通知事件
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件;
4.进程控制
有些进程希望完全控制另一个进程的执行(如Debug进程),该控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

8.2进程间通信的原理

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信机制。
在这里插入图片描述

8.3进程间通信的方式

8.3.1管道(pipe)

管道又名匿名管道,这是一种最基本的IPC机制,由pipe函数创建:

#include <unistd.h>
int pipe(int pipefd[2])

调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端,一个写端:pipefd[0]指向管道的读端,pipefd[1]指向管道的写端。所以管道在用户程序看起来就像一个打开的文件,通过read(pipefd[0])或者write(pipefd[1])向这个文件读写数据,其实是在读写内核缓冲区。
管道使用过程如下:
1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2.父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3.父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信

注意:pipe(匿名管道)只能用于父子进程或者兄弟进程之间。

测试代码如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(){
    int my_pipe[2];
    int ret = pipe(my_pipe);
    if(ret){
        printf("Fail to create pipe");
        return -1;
    }
    printf("my_pipe[0] is :%d my_pipe[1] is :%d \n",my_pipe[0],my_pipe[1]);
    pid_t pid;
    pid = fork();
    if(pid == 0){
        printf("child writing\n");
        close(my_pipe[0]);
        char msg[40];
        for(int i = 0;i <5 ;i++){
            sprintf(msg,"turn %d child send msg",i);
            write(my_pipe[1],msg,strlen(msg));
            sleep(1);
        }
        exit(0);
    }else{
        printf("father reading \n");
        close(my_pipe[1]);
        char msg[40];
        for(int i = 0; i < 5;i++){
            ssize_t s = read(my_pipe[0],msg,sizeof(msg)-1);
            if(s > 0){
                msg[s]='\0';
                printf("receive msg:%s \n",msg);
            } else{
                printf("fail to read");
                exit(1);
            }
        }
    }
    
    exit(0);
}

8.3.2命名管道(FIFO)

上一种进程间通信的方式是匿名的,所以只能用于具有亲缘关系的进程间通信,命名管道的出现正好解决了这个问题。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储文件系统中。命名管道是一个设备文件,因此即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。
命名管道的创建与读写:
1).是在程序中使用系统函数建立命名管道;
2).是在Shell下交互地建立一个命名管道,Shell方式下可使用mknod或mkfifo命令来创建管道,两个函数均定义在头文件sys/stat.h中;

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

返回值:都是成功返回0,失败返回-1;
path为创建的命名管道的全路径名;
mod为创建的命名管道的模式,指明其存取权限;
dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到;
mkfifo函数的作用:在文件系统中创建一个文件,该文件用于提供FIFO功能,即命名管道。
命名管道的特点:
1.命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。所以当使用命名管道的时候必须先open将其打开。
2.命名管道可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间有没有关系。

一个简单的关于命名管道的例子:
server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
void testserver()
{
    int namepipe=mkfifo("myfifo",0666); //创建一个存取权限为0666的命名管道
    if(namepipe == -1){
        perror("mkfifo error");
        exit(1);
    }
    int fd=open("./myfifo",O_RDWR); //打开该命名管道
        if(fd == -1){
        perror("open error");
        exit(2);
    }
    char buf[1024];
    while(1){
        printf("sendto# ");
        fflush(stdout);
        ssize_t s=read(0,buf,sizeof(buf)-1); //从标准输入获取消息
        if(s > 0){
            buf[s-1]='\0'; //过滤掉从标准输入中获取的换行
            if(write(fd,buf,s) == -1){ //把该消息写入到命名管道中
                perror("write error");
                exit(3);
            }
            if(!strcmp(buf,"quit")){
                break;
            }
        }
    }
    close(fd);
}

int main()
{
    testserver();
    return 0;
}

clent.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
void testclient(){
    int fd=open("./myfifo",O_RDWR);
    if(fd == -1){
        perror("open error");
        exit(1);
    }
    char buf[1024];
    while(1){
        ssize_t s=read(fd,buf,sizeof(buf)-1);
        if(s > 0){
            printf("client# %s\n",buf);
        }
        else{ //读失败或者是读取到字符结尾
            perror("read error");
            exit(2);
        }
        if(!strcmp(buf,"quit")){
            break;
        }
    }
    close(fd);
}

int main()
{
    testclient();
    return 0;
}

8.3.3信号量

什么是信号量?
信号量的本质是一种数据操作锁,用来负责数据操作过程中的互斥,同步等功能。
信号量用来管理临界资源的。它本身只是一种外部资源的标识,不具有数据交换功能,而是通过控制其他的通信资源实现进程间通信。 可以这样理解,信号量就相当于是一个计数器。当有进程对它所管理的资源进行请求时,进程先要读取信号量的值:大于0,资源可以请求;等于0,资源不可以用,这时进程会进入睡眠状态直至资源可用。
当一个进程不再使用资源时,信号量+1(对应的操作称为V操作),反之当有进程使用资源时,信号量-1(对应的操作为P操作)。对信号量的值操作均为原子操作。

为什仫要使用信号量?
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。

什仫是临界区?什仫是临界资源?
临界资源:一次只允许一个进程使用的资源。
临界区:访问临界资源的程序代码片段。

信号量的工作原理?
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行等待操作;
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1;
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行了。

信号量相关的函数
信号量的创建

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

返回值:成功返回信号量集合的semid,失败返回-1。
key:可以用函数key_t ftok(const char *pathname, int proj_id);来获取。
nsems:这个参数表示你要创建的信号量集合中的信号量的个数。信号量只能以集合的形式创建。
semflg:同时使用IPC_CREAT和IPC_EXCL则会创建一个新的信号量集合。若已经存在的话则返回-1。单独使用IPC_CREAT的话会返回一个新的或者已经存在的信号量集合。

信号量结合的操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout);

返回值:成功返回0,失败返回-1;
semid:信号量集合的id;

struct sembuf *sops;
struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
}

sem_num:为信号量是以集合的形式存在的,就相当于所有信号在一个数组里面,sem_num表示信号量在集合中的编号;
sem_op:示该信号量的操作(P操作还是V操作)。如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权 。
sem_flg:信号操作标志,它的取值有两种:IPC_NOWAIT和SEM_UNDO。
IPC_NOWAIT:对信号量的操作不能满足时,semop()不会阻塞,而是立即返回,同时设定错误信息;
SEM_UNDO: 程序结束时(不管是正常还是不正常),保证信号值会被设定;
nsops:表示要操作信号量的个数。因为信号量是以集合的形式存在,所以第二个参数可以传一个数组,同时对一个集合中的多个信号量进行操作。

.int semctl(int semid,int semnum,int cmd,...);

semctl()在semid标识的信号量集合上,或者该信号量集合上第semnum个信号量上执行cmd指定的控制命令。根据cmd不同,这个函数有三个或四个参数,当有第四个参数时,第四个参数的类型是union。

union semun{
int val; //使用的值
struct semid_ds *buf; //IPC_STAT、IPC_SET使用缓存区
unsigned short *array; //GETALL、SETALL使用的缓存区
struct seminfo *__buf; //IPC_INFO(linux特有)使用缓存区
};

返回值:成功返回0,失败返回-1;
semid:信号量集合的编号。
semnum:信号量在集合中的标号。

信号量用例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<sys/wait.h>
#define PATHNAME "."
#define PROJID 0x6666
union semun{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */
};
int CreateSemSet(int num);//创建信号量
int GetSemSet(); //获取信号量
int InitSem(int sem_id,int which);
int P(int sem_id,int which); //p操作
int V(int sem_id,int which); //v操作
int DestroySemSet(int sem_id);//销毁信号量
int commSemSet(int num,int flag);

int commSemSet(int num,int flag)
{
    key_t key=ftok(PATHNAME,PROJID);
    if(key == -1)
    {
        perror("ftok error");
        exit(1);
    }
    int sem_id=semget(key,num,flag);
    if(sem_id == -1)
    {
        perror("semget error");
        exit(2);
    }
    return sem_id;
}

int CreateSemSet(int num)
{
    return commSemSet(num,IPC_CREAT|IPC_EXCL|0666);
}

int InitSem(int sem_id,int which)
{
    union semun un;
    un.val=1;
    int ret=semctl(sem_id,which,SETVAL,un);
    if(ret < 0)
    {
        perror("semctl");
        return -1;
    }
    return 0;
}

int GetSemSet()
{
    return commSemSet(0,IPC_CREAT);
}

int SemOp(int sem_id,int which,int op)
{
    struct sembuf buf;
    buf.sem_num=which;
    buf.sem_op=op;
    buf.sem_flg=0; //
    int ret=semop(sem_id,&buf,1);
    if(ret < 0)
    {
        perror("semop error");
        return -1;
    }
    return 0;
}

int P(int sem_id,int which){
    return SemOp(sem_id,which,-1);
}

int V(int sem_id,int which){
    return SemOp(sem_id,which,1);
}

int DestroySemSet(int sem_id)
{
    int ret=semctl(sem_id,0,IPC_RMID);
    if(ret < 0)
    {
        perror("semctl error");
        return -1;
    }
return 0;
}

void testSemSet()
{
    int sem_id=CreateSemSet(1); //创建信号量
    InitSem(sem_id,0);
    pid_t id=fork();
    if(id < 0){
        perror("fork error");
        exit(1);
    }
    else if(id == 0){ //child,打印AA
        printf("child is running,pid=%d,ppid=%d\n",getpid(),getppid());
        while(1){
            P(sem_id,0); //p操作,信号量的值减1
            printf("A");
            usleep(10031);
            fflush(stdout);
            printf("A");
            usleep(10021);
            fflush(stdout);
            V(sem_id,0); //v操作,信号量的值加1
            }
    }
    else //father,打印BB
    {
        printf("father is running,pid=%d,ppid=%d\n",getpid(),getppid());
        while(1){
            P(sem_id,0);
            printf("B");
            usleep(10051);
            fflush(stdout);
            printf("B");
            usleep(10003);
            fflush(stdout);
            V(sem_id,0);
        }
        wait(NULL);
    }
    DestroySemSet(sem_id);
}

int main()
{
    testSemSet();
    return 0;
}

8.3.4共享内存

共享内存原理:
在这里插入图片描述
通过上图可知,共享内存是通过将不同进程的虚拟内存地址映射到相同的物理内存地址来实现的,

与共享内存有关的函数:
1). 创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

返回值:成功返回共享内存的id,失败返回-1;
key:和上面介绍的信号量的semget函数的参数key一样;
size:表示要申请的共享内存的大小,一般是4k的整数倍;
flags:IPC_CREAT和IPC_EXCL一起使用,则创建一个新的共享内存,否则返回-1。IPC_CREAT单独使用时返回一个共享内存,有就直接返回,没有就创建。
2).挂接函数

void *shmat(int shmid);

返回值:返回这块内存的虚拟地址;
shmat的作用是将申请的共享内存挂接在该进程的页表上,是将虚拟内存和物理内存相对应;

3).去挂接函数

int shmdt(const void *shmaddr);

返回值:失败返回-1;
shmdt的作用是去挂接,将这块共享内存从页表上剥离下来,去除两者的映射关系;
shmaddr:表示这块物理内存的虚拟地址。

4).int shmctl(int shmid,int cmd,const void* addr);

shmctl用来设置共享内存的属性。当cmd是IPC_RMID时可以用来删除一块共享内存

共享内存例子:
common.h

#ifndef COMM_H_
#define COMM_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#define PATHNAME "."
#define PROCID 0x6666
#define SIZE 4096*1
int CreatShm();
int GetShm();
//int AtShm();
//int DtShm();
int DestroyShm(int shm_id);
#endif

common.c

#include"common.h"
int CommShm(int flag){
    key_t key=ftok(PATHNAME,PROCID);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int shm_id=shmget(key,SIZE,flag);
    if(shm_id < 0)
    {
        perror("shmget");
        return -2;
    }
    return shm_id;
}

int CreatShm()
{
    return CommShm(IPC_CREAT|IPC_EXCL|0666);
}

int GetShm()
{
    return CommShm(IPC_CREAT);
}

int DestroyShm(int shm_id)
{
    int ret=shmctl(shm_id,IPC_RMID,NULL);
    if(ret < 0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

commem_server.c

#include"common.h"
void testserver()
{
    int shm_id=CreatShm();
    printf("shm_id=%d\n",shm_id);
    char *mem=(char *)shmat(shm_id,NULL,0);
    while(1)
    {
        sleep(1);
        printf("%s\n",mem);
    }
    shmdt(mem);
    DestroyShm(shm_id);
}
int main()
{
	testserver();
	return 0;
}

commem_client.c

#include"common.h"
void testclient()
{
    int shm_id=GetShm();
    char *mem=(char *)shmat(shm_id,NULL,0);
    int index=0;
    while(1)
    {
        sleep(1);
        mem[index++]='A';
        index %= (SIZE-1);
        mem[index]='\0';
    }
    shmdt(mem);
    DestroyShm(shm_id);
}

int main()
{
    testclient();
    return 0;
}

8.3.5消息队列

消息队列原理:
在这里插入图片描述
在内核中开辟一个消息队列,进程A将信息插入到队列中,进程B从队列中取信息。

用户消息缓冲区
无论发送进程还是接收进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下

struct msgbuf {
	long mtype;         /* 消息的类型 */
	char mtext[1];      /* 消息正文 */
};

可通过mtype区分数据类型,同过判断mtype,是否为需要接收的数据;
mtext[]为存放消息正文的数组,可以根据消息的大小定义该数组的长度。

消息队列相关函数:
通过msgget创建消息队列
函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

返回值:成功msgget将返回一个非负整数,即该消息队列的标识码;失败则返回“-1”
key:和上面介绍的信号量的semget函数的参数key一样;
msgflg:由九个权限标志构成,用法和创建信号量时使用的mode模式标志是一样的,这里举两个来说明:

IPC_CREAT
	如果消息队列对象不存在,则创建之,否则则进行打开操作
IPC_EXCL
	如果消息对象不存在则创建之,否则产生一个错误并返回

向消息队列中添加信息:
向消息队列中添加数据,使用到的是msgsnd()函数。
函数原型如下:

int  msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

返回值:成功返回0,失败则返回-1。
msgid: 由msgget函数返回的消息队列标识码
msg_ptr:是一个指针,指针指向准备发送的消息,
msg_sz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
如:
msgflg = IPC_NOWAIT 表示队列满不等待,返回EAGAIN错误

从消息队列中读取消息:
从消息队列中读取消息,我们使用msgrcv()函数,
函数原型如下

int  msgrcv(int msgid, void *msg_ptr, size_t msgsz,
		 long int msgtype, int msgflg);

返回值:成功返回实际放到接收缓冲区里去的字符个数,失败,则返回-1。
msgid: 由msgget函数返回的消息队列标识码
msg_ptr:是一个指针,指针指向准备接收的消息,
msgsz:是msg_ptr指向的消息长度,消息缓冲区结构体中mtext的大小,不包括数据的类型
msgtype:它可以实现接收优先级的简单形式
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息 
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
注意
msgtype>0且msgflg=MSC_EXCEPT,接收类型不等于msgtype的第一条消息

消息队列控制函数:
函数原型:

int  msgctl(int msqid, int command, strcut msqid_ds *buf);

返回值:如果操作成功,返回“0”;如果失败,则返回“-1”
msqid: 由msgget函数返回的消息队列标识码
command:是将要采取的动作

消息队列的一个例子:
common.h

#ifndef COMMON_H_
#define COMMON_H_

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
#define PATHNAME "."
#define PROCID 0x6666
#define SIZE 2048

struct msgstru
{
    long msgtype;
    char msgtext[SIZE]
};


int CreatMsgQueue();
int GetMsgQueue();
int SendMsg(int msg_id,struct msgstru* msg);
int RecMsg(int msg_id,struct msgstru* msg);
int DestroyMsgQueue(int msg_id);

#endif

common.c

#include "common.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int CommMsq(int flag){
    key_t key=ftok(PATHNAME,PROCID);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }
    int msgid=msgget(key,flag);
    if(msgid < 0)
    {
        perror("msgget error");
        return -2;
    }
    return msgid;
}

int CreatMsgQueue(){
    return CommMsq(IPC_CREAT|IPC_EXCL|0666);
}
int GetMsgQueue(){
    return CommMsq(IPC_CREAT|0666);
}

int SendMsg(int msg_id,struct msgstru* msg){
    if(msgsnd(msg_id, msg, sizeof(msg->msgtext),IPC_NOWAIT) < 0){
        perror("msgsend error");
        return -1;
    }
    return 0;
}

int RecMsg(int msg_id,struct msgstru* msg){
    int ret;
    if((ret=msgrcv(msg_id,msg,sizeof(msg->msgtext),msg->msgtype,IPC_NOWAIT)) < 0){
        // perror("msgrcv error");
        return -1;
    }
    return ret;
}

int DestroyMsgQueue(int msg_id){
    return msgctl(msg_id,IPC_RMID,0);
}

server.c

#include "common.h"
#include <stdio.h>
#include <stdlib.h>
int main(){
    struct  msgstru msg;
    char str[2048];
    int msg_type;
    int msg_id = CreatMsgQueue();
    if(msg_id<0){
        msg_id = GetMsgQueue();
        if(msg_id<0)
            exit(-1);
    }

    while (1)
    {
        printf("input message type:\n");
        scanf("%d",&msg_type);
        if(msg_type == 0){
            break;
        }

        printf("input msg to be sent:\n");
        scanf("%s",str);
        strcpy(msg.msgtext,str);
        msg.msgtype = msg_type;
        if(SendMsg(msg_id,&msg) < 0){
            printf("fail to send msg \n");
            exit(-1);
        }
    }
    DestroyMsgQueue(msg_id);
    exit(0);
}

client.c

#include "common.h"
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
int main(){
    struct  msgstru msg;
    int msg_type;
    int msg_id = GetMsgQueue();
    if(msg_id<0){
        printf("fail to create msg queue");
        exit(-1);
    }

    while (1)
    {
       msg.msgtype = 1;
       if(RecMsg(msg_id,&msg) >0){
            if(!strcmp(msg.msgtext,"quit")){
                printf("client over \n");
                exit(0);
            } else{
                printf("rev msg :%s\n",msg.msgtext);
            }
       }

    }
    
    DestroyMsgQueue(msg_id);
    exit(0);
}

8.3.6信号量,共享内存,消息队列的查看与删除

查看指令
ipcs -a  是默认的输出信息 打印出当前系统中所有的进程间通信方式的信息
ipcs -m  打印出使用共享内存进行进程间通信的信息
ipcs -q   打印出使用消息队列进行进程间通信的信息
ipcs -s  打印出使用信号进行进程间通信的信息
ipcs -t   输出信息的详细变化时间
ipcs -u  输出当前系统下ipc各种方式的状态信息(共享内存,消息队列,信号)
删除指令
ipcrm -M shmkey  移除用shmkey创建的共享内存段
ipcrm -m shmid    移除用shmid标识的共享内存段
ipcrm -Q msgkey  移除用msqkey创建的消息队列
ipcrm -q msqid  移除用msqid标识的消息队列
ipcrm -S semkey  移除用semkey创建的信号
ipcrm -s semid  移除用semid标识的信号
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux实验二进程管理主要涉及到进程的创建、终止、查看和管理等方面。在Linux系统中,进程是操作系统中最基本的执行单元,它是程序在执行过程中的一个实例。通过进程管理,我们可以控制和管理系统中的各个进程,从而保证系统的稳定性和安全性。 在Linux系统中,我们可以使用命令行工具来进行进程管理。其中,常用的命令包括ps、kill、top、nice等。通过这些命令,我们可以查看系统中正在运行的进程、终止指定的进程、调整进程的优先级等。 此外,Linux系统还提供了一些图形化的进程管理工具,如System Monitor、htop等。这些工具可以更直观地显示系统中的进程信息,并提供更丰富的操作选项。 总之,进程管理是Linux系统中非常重要的一部分,它对于系统的稳定性和安全性具有重要的影响。掌握进程管理的基本原理和常用命令,可以帮助我们更好地管理和维护Linux系统。 ### 回答2: Linux实验二进程管理主要涉及了进程的创建、运行、退出等基本操作。在Linux系统中,进程是系统资源调度的基本单位,也是程序运行的基本单位。因此,在Linux操作系统中,对于进程管理的实现显得十分重要。 在实验中,我们学习了如何通过编程的方式进行进程的创建。首先,我们需要使用fork()函数来创建新的进程。这个函数在调用的进程内部生成一个子进程,子进程与父进程共享代码、数据、文件等资源,并且这两个进程的运行是相对独立的。在子进程中可以使用exec()函数来加载新的程序,从而实现一个新的进程。在实验中,我们尝试了几种不同的fork()和exec()函数的组合方式,例如:子进程执行一个程序,子进程替换成另一个程序等。这些不同的方式可以更加灵活地控制进程的创建和运行。 其次,我们学习了如何使用信号来管理进程。在Linux系统中,信号是一种异步的通知机制,它可以在运行中的进程之间进行通信,例如进程的退出通知等。我们使用signal()函数来安装信号处理程序,这个处理程序在特定的信号到来时执行。例如,我们尝试了在子进程中安装SIGINT信号处理程序,当在终端输入Ctrl+C时,子进程捕获到这个信号并调用处理程序。 另外,我们学习了如何使用管道来进行进程间通信。在Linux系统中,管道是进程间通信的一种常用方式。我们使用pipe()函数来创建管道,然后使用fork()函数创建子进程和父进程,两个进程之间可以通过管道来传输数据。在实验中,我们使用管道来实现父进程向子进程发送数据的过程,这个过程需要通过write()函数写入数据到管道中,子进程可以通过read()函数来读取管道中的数据。 综上所述,实验二进程管理是一个非常实用的实验,我们通过实验学习了进程的创建、运行、退出等基本操作,学了如何使用信号和管道进行进程通信,掌握了进程管理中一些常用的技巧和方法,这些技能对于我们后续的Linux系统学习和工作都是非常有帮助的。 ### 回答3: Linux实验二进程管理是计算机操作系统课程中的一项实验任务,主要涉及如何创建、管理和控制进程。本实验要求学生在Linux操作系统下使用命令行工具,通过编写和运行C程序来实现进程的创建和管理。 在Linux中,进程是操作系统的基本单位,每个进程都拥有自己的资源和内存空间。管理进程的操作可以通过Linux系统内核提供的一系列命令和系统调用来完成。常用的进程管理命令包括ps、kill、top等,它们可以帮助用户查看进程列表、杀死进程或者查看进程的状态。 本实验要求使用C语言编写程序并通过命令行编译执行。首先要实现进程的创建,通过fork系统调用可以创建一个子进程。父进程调用fork返回子进程的pid,而子进程返回0,可以通过判断返回值来确定当前程序是父进程还是子进程。子进程可以调用exec系统调用来执行其他程序,从而实现进程间的交互和通信。 在进程管理中,还需要对进程进行调度和控制。可以通过信号来控制进程的行为,kill命令可以向指定进程发送不同的信号,例如SIGTERM信号可以结束进程,SIGINT信号可以停止进程。另外还可以使用nice命令来调整进程的优先级,通过top命令可以查看进程的状态和占用的系统资源。 总的来说,Linux实验二进程管理是一项非常重要的实验任务,通过学习和实践可以帮助学生更深入地理解进程管理的原理和技术,为以后的系统管理和开发打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值