杭电(杭州电子科技大学)操作系统实验三:Linux进程管理

阅读: 评论:0

杭电(杭州电子科技大学)操作系统实验三:Linux进程管理

杭电(杭州电子科技大学)操作系统实验三:Linux进程管理

目录

    • 前言
    • 实验内容
    • 实验一
    • 实验二
    • 实验三
    • 实验四

前言

由于是小组实验,我分配被问到的是实验三的sender线程的实现,因此只记录了这一块内容可能问到的问题,不过代码里的注释很详尽了,可以自行观看。

实验内容

(1)实现一个模拟的shell
(2)实现一个管道通信程序
(3)利用Linux的消息队列通信机制实现两个线程间的通信
(4)利用Linux的共享内存通信机制实现两个进程间的通信

实验一

可能问到的问题:
①execl()函数的参数以及其实现的原理
②fork()函数

#include <unistd.h> //exit excel 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX_CMD_LEN 20 //输入命令最大长度
#define CMD_COLLECTION_LEN 4 //命令数组长度#define INVALID_COMMAND -1 //未识别命令
#define EXIT 0 //正常退出
#define CMD_1 1
#define CMD_2 2
#define CMD_3 3#define TRUE 1char *cmdStr [CMD_COLLECTION_LEN] = {"exit","cmd1","cmd2","cmd3"};
//命令数组int getCmdIndex(char *cmd)
{//判断输入命令是否合法int i;for(i=0;i<CMD_COLLECTION_LEN;i++){if(strcmp(cmd,cmdStr[i])==0){return i;}}return -1;
}void myFork(int cmdIndex)
{pid_t pid;//pid_t 实际上是int型if((pid = fork())<0)//fork 子进程是父进程的副本 {//fork返回-1说明创建失败,打印fork errorprintf("fork error");exit(0);//退出程序}else if(pid == 0)//fork返回0说明当前运行的是子进程,所以可以进行替换{	int execl_status = -1;//接受execl函数的返回值printf("child is running");switch(cmdIndex){case CMD_1:execl_status = execl("./cmd1","cmd1",NULL);//执行程序路径,参数名,最后一个参数须用空指针NULL作结束break;case CMD_2:execl_status = execl("./cmd2","cmd2",NULL);break;case CMD_3:execl_status = execl("./cmd3","cmd3",NULL);break;default:printf("Invalid Commandn");break;}if(execl_status<0)//程序替换失败,即运行失败{printf("fork error");exit(0);}printf("fork successn");exit(0);}else{return;}
}
void runCMD(int cmdIndex)
{switch(cmdIndex){case INVALID_COMMAND://未识别命令printf("COMMAND NOT FOUND n");break;case EXIT://退出命令exit(0);break;default:myFork(cmdIndex);break;}
}
int main()
{pid_t pid;//进程号 char cmdStr[MAX_CMD_LEN];int cmdIndex;while(TRUE){printf("n---Input your command > ");scanf("%s",cmdStr);cmdIndex = getCmdIndex(cmdStr);//判断合法性命令runCMD(cmdIndex);//给出对应反应wait(NULL); //父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出//参数是NULL说明我们不在意子进程是如何结束的 存在一套宏专门用来记录进程结束状态(是否正常退出等)printf("waiting for next command");}
}

实验二

#include "fcntl.h"
#include "semaphore.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/ipc.h"
#include "sys/sem.h"
#include "sys/types.h"
#include "sys/wait.h"
#include "unistd.h"
#define BUF_MAX_SIZE 8192// 如果x为假,则报错
void CHECK(int x){if(!x){printf("error appear");exit(-1);}	
}
int main(int argc, char **argv) {int pipefd[2], pid, i = 0;int flag = 0;char buf[BUF_MAX_SIZE];//管道缓冲池char str[BUF_MAX_SIZE];sem_t *write_mutex;//sem_t本质上是一个长整形 对于管道进行写操作的互斥信号量sem_t *read_mutex1;//进程二三都发完消息的信号量sem_t *read_mutex2;write_mutex = sem_open("pipe_test_wm", O_CREAT | O_RDWR, 0666, 0);read_mutex1 = sem_open("pipe_test_rm_1", O_CREAT | O_RDWR, 0666, 0);read_mutex2 = sem_open("pipe_test_rm_2", O_CREAT | O_RDWR, 0666, 0);/*创建有名信号量,默认值都为0sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);name   	信号量的外部名字oflag   选择创建或打开一个现有的信号量O_CREAT | O_RDWR 若不存在则创建,若存在则直接打开mode 	权限位权限0666:第一个 0 表示这个数是八进制第一个 6 表示文件拥有者有读写权,但没有执行权限第二个 6 表示文件拥有者同组用户有读写权限,但没有执行权限第三个 6 表示其它用户有读写权限,但没有执行权限value 	信号量初始值 该初始不能超过 SEM_VALUE_MAX,这个常值必须低于为32767此次实验中默认为0		返回值:成功时返回指向信号量的指针,出错时为SEM_FAILED*/memset(buf, 0, BUF_MAX_SIZE);memset(str, 0, BUF_MAX_SIZE);/*在<string.h>下  void *memset(void *s, int c, size_t n) 初始化数组数组名 0 数组长度*/CHECK(pipe(pipefd) == 0);// 创建管道并检查操作是否成功/*<unistd.h>int pipe(int pipefd[2]); 成功:0;失败:-1pipefd[0]:读管道 pipefd[1]:写管道两个文件描述符引用,一个表示读端,一个表示写端管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。*/CHECK((pid = fork()) >= 0);// 创建第一个子进程并检查操作是否成功// 第一个子进程,利用非阻塞写测试管道大小if (pid == 0) {int count = 0;close(pipefd[0]);//关闭读端int flags = fcntl(pipefd[1], F_GETFL);//获取写端状态/*int fcntl(int fd, int cmd);F_GETFL    取得fd的文件状态标志fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符.            针对cmd的值,fcntl能够接受第三个参数(arg)fcntl函数有5种功能:1.复制一个现有的描述符(cmd=F_DUPFD).2.获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).3.获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).4.获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).5.获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).*/// 管道默认是阻塞写,通过`fcntl`设置成非阻塞写,在管道满无法继续写入时返回-EAGAIN,作为循环终止条件fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);/*int fcntl(int fd, int cmd, long arg);F_SETFL 设置文件描述符状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响*/// 写入管道while (!flag) {n = write(pipefd[1], buf, BUF_MAX_SIZE);//一次装入8192B数据 ,因为非阻塞写,所以写满了就会跳出循环,算出管道大小if (n == -1) {flag = 1;} else {count++;printf("children 1 write %dBn", n);}}printf("space = %dKBn", (count * BUF_MAX_SIZE) / 1024);exit(0);}// 创建第二个子进程并检查操作是否成功CHECK((pid = fork()) >= 0);if (pid == 0) {//当前子进程sem_wait(write_mutex);//检查是否有人在写,有则等待close(pipefd[0]);//关闭读端n = write(pipefd[1], "This is the second children.n", 29);//写入数据printf("children 2 write %dBn", n);sem_post(write_mutex);//释放写权限sem_post(read_mutex1);//告诉父进程我信息发完了exit(0);//退出该进程}// 创建第三个子进程并检查操作是否成功CHECK((pid = fork()) >= 0);if (pid == 0) {sem_wait(write_mutex);close(pipefd[0]);n = write(pipefd[1], "This is the third children.n", 28);printf("children 3 write %dBn", n);sem_post(write_mutex);sem_post(read_mutex2);exit(0);}// 等待第一个子进程运行完成,父进程继续运行wait(0);close(pipefd[1]);//关闭写端口,父进程读取子进程一的数据清空管道int flags = fcntl(pipefd[0], F_GETFL);// 取得文件状态标志// 设置非阻塞性读,作为循环结束标志fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);while (!flag) {n = read(pipefd[0], str, BUF_MAX_SIZE);if (n == -1) {flag = 1;} else {printf("%dB readn", n);}}sem_post(write_mutex);//释放写权限// 等待子进程二、三写入完毕sem_wait(read_mutex1);sem_wait(read_mutex2);n = read(pipefd[0], str, BUF_MAX_SIZE);printf("%dB readn", n);for (i = 0; i < n; i++) {printf("%c", str[i]);}sem_close(write_mutex);sem_close(read_mutex1);sem_close(read_mutex2);/*int sem_close(sem_t *sem)关闭有名信号量 成功返回0,失败返回-1一个进程终止时,内核对其上仍打开的所有有名信号量自动执行关闭操作,不论进程是自愿还是非自愿终止,这种自动关闭都会发生但关闭一个信号量并没有将他从系统中删除Posix有名信号灯至少是随内核持续的:即使当前没有进程打开着某个信号灯,他的值仍然保持多进程打开时候,一边sem_close后,仍可以打开已经打开的信号量*/sem_unlink("pipe_test_wm");sem_unlink("pipe_test_rm_1");sem_unlink("pipe_test_rm_2");/*int sem_unlink(const char *name)系统中删除信号灯 成功返回0,失败返回-1有名信号灯使用sem_unlink从系统中删除每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除也就是要等待最后一个sem_close发生*/return 0;
}

实验三

这里的代码实际上不算很长,只是注释比较多。(不过实验三确实是四个试验里代码量最大的)

可能问到的问题:
①msgsnd()函数的参数
②msgrcv()函数的参数

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>#define send_type	1 // 2种 消息类型
#define recv_type	2#define send_1_to_recv	1			// 4种 消息走向
#define send_2_to_recv	2
#define recv_to_send_1	3
#define recv_to_send_2	4void *send_thread_1(void *arg);
void *send_thread_2(void *arg);
void *recv_thread(void *arg);sem_t send_psx, recv_psx, final_recv_1, final_recv_2;//定义4个信号量类型
pthread_t send_pid_1, send_pid_2, recv_pid;	//声明线程IDint send_1_over = 0;// sender1线程 是否已经结束
int send_2_over = 0;// sender2线程 是否已经结束struct msgbuf
{long mtype;char mtext[256];int mdir;// message direction表示消息的走向
};int msgid;void *send_thread_1(void *arg)
{char info[256];// 消息发送区struct msgbuf s_msg;// 消息缓存区pe = send_type;// 1s_msg.mdir = send_1_to_recv;//消息走向 从发送到接受区while (1) {//用户输入sem_wait(&send_psx);//申请发送权限printf("<Sender1> send: ");scanf("%s", info);//当用户输入exit时,发送信息停止if ((strcmp(info, "exit") == 0)) {strcpy(, "end1");msgsnd(msgid, &s_msg, sizeof(struct msgbuf), 0);/*将消息写入到消息队列<sys/types.h><sys/ipc.h><sys/msg.h>int msgsnd(int msqid[消息队列标识符], const void *msgp[发送给队列的消息], size_t msgsz[要发送消息的大小], int msgflg[消息无法写入时的选择])msgflg:0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。*/sem_post(&recv_psx);//给recv一个信号,有信息在信息队列里面了break;}//正常输入时strcpy(, info);msgsnd(msgid, &s_msg, sizeof(struct msgbuf), 0);//追加一条消息到消息队列中sem_post(&recv_psx);}sem_wait(&final_recv_1);//检查接收线程有没有发送over1消息给线程1   final_recv_1 处理 send_thread_1 最后一次接受消息的问题msgrcv(msgid, &s_msg, sizeof(struct msgbuf), recv_type, 0);//从消息队列读取消息   recv_type 2  0 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待/*从标识符为msqid的消息队列读取消息并存于s_msg中,读取后把此消息从消息队列中删除#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>ssize_t msgrcv(int msqid[消息队列标识符], void *msgp[存放消息的结构体], size_t msgsz[要接收消息的大小], long msgtyp[接受什么样的消息],int msgflg[没有该类型的消息msgrcv函数应该做什么])成功:实际读取到的消息数据长度出错:-1,错误原因存于error中错误代码E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROREIDRM:标识符为msqid的消息队列已被删除EACCESS:无权限读取该消息队列EFAULT:参数msgp指向无效的内存地址ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读EINTR:等待读取队列内的消息情况下被信号中断*/printf("<Sender1> receive: %sn", );sem_post(&send_psx);//释放发送权限if (send_1_over && send_2_over){//  2个 sender线程 都发送过 'end' 且收到过 'over' 后,将移除消息队列msgctl(msgid, IPC_RMID, 0);	// 移除消息队列/*<sys/types.h><sys/ipc.h><sys/msg.h>int msgctl ( int msgqid, int cmd, struct msqid_ds *buf[消息队列管理结构体] );IPC_STAT 读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中IPC_SET  设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数IPC_RMID 从系统内核中移走消息队列因为是移除操作,所以buf置零了*/}pthread_exit(NULL);	// 类比进程的终止 exit()
}void *send_thread_2(void *arg)
{char info[256];// 消息发送区struct msgbuf s_msg;// 消息缓存区pe = send_type;s_msg.mdir = send_2_to_recv;while (1) {sem_wait(&send_psx);printf("<Sender2> send: ");scanf("%s", info);if ((strcmp(info, "exit") == 0)) {strcpy(, "end2");msgsnd(msgid, &s_msg, sizeof(struct msgbuf), 0);sem_post(&recv_psx);break;}strcpy(, info);msgsnd(msgid, &s_msg, sizeof(struct msgbuf), 0);// 追加一条消息到消息队列中sem_post(&recv_psx);}sem_wait(&final_recv_2);// final_recv_2 处理 send_thread_2 最后一次接受消息的问题msgrcv(msgid, &s_msg, sizeof(struct msgbuf), recv_type, 0);//从消息队列中读一条消息printf("<Sender2> receive: %sn", );sem_post(&send_psx);if (send_1_over && send_2_over){// 2个 sender 线程 都发送过 'end' 且收到过 'over' 后,将移除消息队列msgctl(msgid, IPC_RMID, 0);// 移除消息队列}pthread_exit(NULL);	// 类比进程的终止 exit()
}void *recv_thread(void *arg)
{struct msgbuf r_msg;// 消息缓存区while (1) {sem_wait(&recv_psx);//首先检查消息队列里有没有消息msgrcv(msgid, &r_msg, sizeof(struct msgbuf), send_type, 0);//从消息队列中读取一条消息存到消息缓存区if (r_msg.mdir == send_1_to_recv){// 根据消息走向判断来源if (strcmp(, "end1") == 0) {//是来自发送方的最后一条消息,recv 将“over1”消息写入消息队列strcpy(, "over1");pe = recv_type;r_msg.mdir = recv_to_send_1;msgsnd(msgid, &r_msg, sizeof(struct msgbuf), 0);printf("<Receive_thread> receive 'end1' from <Sender1>, returning 'over1'...n");sem_post(&final_recv_1);//告诉线程一收到最后一条消息了send_1_over = 1;//线程一结束标志}else {printf("<Receive_thread> receive %s from <Sender1>n", );//打印接收到的消息sem_post(&send_psx);//告诉线程我收到消息了}}else if (r_msg.mdir == send_2_to_recv) {// 根据消息走向判断来源if (strcmp(, "end2") == 0) {strcpy(, "over2");pe = recv_type;r_msg.mdir = recv_to_send_2;msgsnd(msgid, &r_msg, sizeof(struct msgbuf), 0);printf("<Receive_thread> receive 'end2' from <Sender2>, returning 'over1'...");sem_post(&final_recv_2);send_2_over = 1;}else {printf("<Receive_thread> receive %s from <Sender>n", );sem_post(&send_psx);}}if (send_1_over && send_2_over){// 2个 sender线程 都发送过 'end' 且收到过 'over' 后,将跳出循环,结束当前线程break;}}pthread_exit(NULL);//退出程序
}int main(void)
{sem_init(&send_psx, 0, 1);// pshared = 0,信号量在同一进程的多线程中同步 且线程之间发送信息互斥sem_init(&recv_psx, 0, 0);//告诉recv信息对列有几条数据sem_init(&final_recv_1, 0, 0);//来自接收线程对线程一的最后一条消息的应答sem_init(&final_recv_2, 0, 0);//来自接收线程对线程二的最后一条消息的应答/*extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));  sem为指向信号量结构的一个指针pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享value给出了信号量的初始值*/msgid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);// 创建消息队列/*int sys_msgget (key_t key, int msgflg){int id;struct msqid_ds *msq;// 设置了私有的标记则直接创建一个新的消息队列if (key == IPC_PRIVATE) return newque(key, msgflg);// 找不到if ((id = findkey (key)) == -1) { //key not used// 有传IPC_CREAT标记则创建一个新的队列,否则返回找不到if (!(msgflg & IPC_CREAT))return -ENOENT;return newque(key, msgflg);}// 找到了,但是设置了下面两个标记位说明该消息队列需要由当前进程创建才会返回成功if (msgflg & IPC_CREAT && msgflg & IPC_EXCL)return -EEXIST;msq = msgque[id];// 无效if (msq == IPC_UNUSED || msq == IPC_NOID)return -EIDRM;// 检查权限if (ipcperms(&msq->msg_perm, msgflg))return -EACCES;return (unsigned int) msq->msg_perm.seq * MSGMNI + id;}函数声明: int msgget ( key_t key, int msgflg)参数: key:函数ftok的返回值或IPC_PRIVATE(新建一个队列)。 msgflag: IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。 IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1.*/if (msgid < 0) {printf("[*] Error: msgget() return errorn");exit(1);}pthread_create(&send_pid_1, NULL, send_thread_1, NULL);// 创建线程pthread_create(&send_pid_2, NULL, send_thread_2, NULL);pthread_create(&recv_pid, NULL, recv_thread, NULL);/*int pthread_create(pthread_t *tidp[指向线程标识符的指针],const pthread_attr_t *attr[设置线程属性],void *(*start_rtn)(void*)[线程运行函数的起始地址],void *arg[运行函数的参数]);*/pthread_join(send_pid_1, NULL);	// 阻塞调用 send / receive 线程,否则会出现main函数启动后马上退出pthread_join(send_pid_2, NULL);pthread_join(recv_pid, NULL);/*头文件 : #include <pthread.h>函数定义: int pthread_join(pthread_t thread, void **retval);描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。返回值 : 0代表成功。 失败,返回的则是错误号。*/return 0;
}

实验四

这个实验用了三个信号量,就类似于书上的读者写者问题的实现。

reveiver.c

#include "my_shm.h"int main(){my_init();int flag=0;while(1){sem_wait(sem_full);sem_wait(sem_mutex);char recv[100];strcpy(recv, (char *)shmp);printf("receive: %sn",recv);if(strcmp(recv, "exit") == 0){char send[100]="over";memset((char *)shmp, '', 1024);strcpy((char *)shmp, send);flag=1;}sem_post(sem_empty);sem_post(sem_mutex);  if(flag) break;}shmdt(shmp);printf("quit receiver!n");exit(0);
}

sender.c

#include "my_shm.h"int main(){my_init();int flag=0;while(1){sem_wait(sem_empty);sem_wait(sem_mutex);if(flag){char recv[100];strcpy(recv, (char *)shmp);printf("respond: %sn", recv);break;}printf("send: ");char send[100];scanf("%s", send);if(strcmp(send,"exit")==0)flag=1;memset((char *)shmp, '', 1024);strcpy((char *)shmp, send);sem_post(sem_mutex);sem_post(sem_full);} sem_unlink("empty");sem_unlink("full");sem_unlink("mutex");shmdt(shmp);shmctl(shmid, IPC_RMID, NULL);//shmid cmd bufprintf("quit sender!n");  exit(0);
}

my_shm.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>sem_t *sem_empty;
sem_t *sem_full;
sem_t *sem_mutex;int shmid;
void *shmp;void my_init(){sem_empty = sem_open("empty", O_CREAT, 0666, 1);sem_full = sem_open("full", O_CREAT, 0666, 0);sem_mutex = sem_open("mutex", O_CREAT, 0666, 1);shmid = shmget(0x0127, 1024, 0666|IPC_CREAT); shmp = shmat(shmid, NULL, 0);//shmid shmaddr shmflag
}

本文发布于:2024-01-28 03:40:39,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17063844454520.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23