实验三:linux进程管理
1)实现一个模拟的shell
问题:编写三个cmd1.c
、cmd2.c
、cmd3.c
程序,功能自定。需要一个仿shell的程序和实现类似执行grep
、find
等命令的程序。通过在仿shell中输入命令(字符串和参数),创建子进程来执行相应的命令程序,父进程需要等待子进程处理结束后,才能再去查看是否有新的命令。输入“exit
”退出shell程序。输入无效命令则显示“Command not found”
,等待下一个新命令。
前说下,刚开始把问题看复杂了,除了要求之外还写了cd
、pwd
、exit
、mkdir
,所以可以自行删改(不麻烦),或者有兴趣可以加更多的命令玩玩 : )
处理方法:
myshell.c
:
- 提供循环输入命令模块
- 解析命令
- 判断命令是内置命令还是外置命令(可以不用)
- 内置命令直接调用函数,外置命令用
fork()
来创建一个子进程, 在进程中执行命令程序采用exec族的函数来调用函数执行。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/syscall.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#define MAX_BUF 80
#define MAX_CMD 80
#define MAX_ARG_NR 16
int cmd_len; //输入的命令长度(包含换行符)
//////////////////////////////////////////////////////////////////////////
int main()
{
char buf[MAX_BUF];
char command[MAX_CMD+1];
system("clear");
printf("Songshell: support cd/pwd/mkdir/exit/find/grep\n");
while(1){
getcwd(buf, sizeof(buf));
printf("➜ [%s] ", buf);
fflush(stdout); //输出上一行内容
memset(command,0, sizeof(command));
if((cmd_len = read(0, command, MAX_CMD)) < 2) //仅有回车,cmd_len=1
printf("Command Error! Pleace Try Again\n");
else{
ToProcess(command);
}
}
return 0;
}
ssize_t read(int fd, void *buf, size_t count);
read(0, command, MAX_CMD))
从标准输入文件中读取最大长度为MAX_CMD
并带有回车的字符串写入command
,当除了回车还有其他字符时,调用ToProcess(command)
//执行命令
int ToProcess(const char *command)
{
char *argv[MAX_CMD];
char buf[MAX_CMD];
strcpy(buf, command);
buf[cmd_len-1] = ' ';
buf[cmd_len] = '\n';
ParsePalam(buf, argv);
if(argv[0] == NULL) //命令为空
return 0;
if(!JudgeBuildin(argv)){
//不是内置命令
pid_t child = fork();
if(child > 0){
int status = 0;
wait(&status);
}
else if(child == 0){ //子进程
if(!strcmp(argv[0], "mkdir"))
execl("/home/exp3/myshell/mkdir_cmd", argv[1], NULL);
else if(!strcmp(argv[0], "command1"))
execl("/home/exp3/myshell/command1", argv[0], NULL);
else if(!strcmp(argv[0], "command2"))
execl("/home/exp3/myshell/command2", argv[0], NULL);
else if(!strcmp(argv[0], "command3"))
execl("/home/exp3/myshell/command3", argv[0], NULL);
else if(!strcmp(argv[0], "find"))
execv("/usr/bin/find",argv);
else if(!strcmp(argv[0], "grep"))
execv("/usr/bin/grep", argv);
}
else if(child == -1){
printf("failed: no child process is created!\n");
}
}
return 0;
}
P7-8:为了让后面ParsePalam()
函数更好解析输入的命令参数。在最后一个参数的后面添加空格, 和其他参数结构保持一致(这里不加,解析会出错)。
P9:解析完参数,包括命令及参数都按输入顺序存入char *argv[]
指针数组中,调用形式为argv[0]
,argv[1]
..
P12:一般而言,在一个脚本里执行一个外部命令(普通的可执行文件)时,shell 会 fork 出一个子进程,然后再用 exec族函数来执行这个程序;但是,bash shell 的内置命令(builtin)却不会这样,它们是直接执行的。所以,等价的内置命令的执行速度会比执行外部命令要来的快。
可以通过type或者which命令来查看命令类型:提到builtin
就是内置,打印出目录如/usr/bin/xxxx
这样的就是外置命令
如果不是内置命令就要涉及创建子进程和执行外部程序的实现了
通过终端输入man 2 fork
查看fork的编程手册。
P15:child>0
,返回的是子进程的PID号,说明当前是父进程,按要求,父进程不需要做什么事,等着就完事了。man 2 wait
,看下头文件,函数用最简单的wait(int *wstatus)
P19:child == 0
,说明是子进程,于是要调用外部程序了,man exec
查看exec族的相关信息。exec族博客
拿P21的execl("/home/exp3/myshell/mkdir_cmd", argv[1], NULL);
举例,execl的参数依次是,调用程序的路径名, 参数0,参数1,.....,NULL结尾。这里的argv[1]就是要创建的目录名
mkdir_cmd.c
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
if(mkdir(argv[0], 0755) == -1){
printf("mkdir failed\n");
}
return 0;
}
提醒,main
参数一定别忘记了argc
,血泪教训,参数就是顺序写入到argv
中,我只传入了一个参数。
P23-27:按照题目要求,创建了三个command*.c
程序并编译生成了command *
可执行文件用于测试
//command1.c
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("command1 print\n");
return 0;
}
P29:execv("/usr/bin/find",argv);
,execv不像execl要一个一个传参数,而是直接提供指针数组就行了。因为find命令可以带很多参数就用这个了
P33:child == -1
,fork
失败
注意:因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。所以创建子进程来提供exec执行命令程序的空间。(如下图可以看到,execl调用成功后不会打印success)
//输入参数带引号的情况,会将引号看作字符串的一部分
//下面是加了去引号的版本
int ParsePalam(char *buf, char** argv)
{
int argv_idx, argc, flag = 0;
argv_idx = argc = 0;
while(argv_idx < MAX_ARG_NR) //初始化
argv[argv_idx++] = NULL;
while(1){
while(*buf == ' ') //跳过空格
buf++;
if(*buf == '\n') //碰到回车循环结束
break;
if(*buf == '"'){ //处理字符串的双引号
flag = 1;
argv[argc] = ++buf;
}
else
argv[argc] = buf; //记录参数首地址
while(*buf != ' ' && *buf) //确定参数结束地址
buf++;
if(!flag)
*buf++ = '\0'; //添加字符串结束符
else{
*(buf++-1) = '\0';
flag = 0;
}
if(argc > MAX_ARG_NR){ //超过最大参数数量error
printf("argc is too large!\n");
return -1;
}
argc++;
}
return argc; //返回参数数量
}
解析参数的方法是用《操作系统真相还原》里的方法。
主要思路是argv[i]
记录第i个参数的首地址,并把参数后面的空格换成结束符
举例:终端输入find . -name "main.c"\n
,经过ParsePalam
函数处理就变成find\0.\0-name\0"main,c"\0\n
argv[0]="find"
, argv[1]="."
, argv[2]="-name"
, argv[3] = "main.c"
//判断是否是内置命令
int JudgeBuildin(char **argv)
{
if(!strcmp(argv[0], "cd")){
if(chdir(argv[1])) //return -1 means error
printf("dir isn't exist\n");
return 1;
}
else if(!strcmp(argv[0], "pwd")){
char buf[MAX_BUF];
getcwd(buf, sizeof(buf));
printf("%s\n", buf);
return 1;
}
else if(!strcmp(argv[0], "exit") || !strcmp(argv[0], "q") ){
exit(0);
}
return 0;
}
这部分没啥难度,也不是实验要求。
实例运行:
进程间通信 IPC(interprocess communication),指的是在多个并发进程之间进行的信息交换。
- 低级方法:锁机制和信号量机制
- 高级方法:管道通信、共享存储器、消息队列、客户-服务器通信(套接字和远程过程调用)
2) 阻塞性管道通信
问题:
- 父进程创建一个无名管道和三个子进程,三个子进程利用管道与之通信,父进程只有在全部子进程发送完消息后再接收数据。要求使用Posix信号量机制实现管道的互斥访问
- 用有名管道测试管道的大小
同步互斥关系
- 所有子进程对管道缓冲区的写数据存在互斥关系,使用
write_psem=1
进行约束 - 父进程要等待所有子进程写完数据后才能读,存在同步关系,使用
read_psem[i]=0
进行约束,当所有read_psem[i]=1
时,父进程开始写数据
//问题一
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#define MAX_BUF 1024*64
#define CHILD_PROCESS_NUM 3
sem_t *write_psem;
sem_t *read_psem[CHILD_PROCESS_NUM];
char buf[MAX_BUF];
char *SEM_NAME[4] = {"sem0", "sem1", "sem2", "sem3"}; //给信号量命名
int main(int argc, char *argv[])
{
int fd[2], pid[CHILD_PROCESS_NUM], i, count, ret_len, number;
for(i = 0; i < CHILD_PROCESS_NUM; ++i)
read_psem[i] = sem_open(SEM_NAME[i], O_CREAT, 0666, 0);
write_psem = sem_open(SEM_NAME[3], O_CREAT, 0666, 1);
if(pipe(fd) == -1){
printf("pipe error\n");
return -1;
}
for(i = 0; i < CHILD_PROCESS_NUM; ++i){
if( (pid[i] = fork()) == -1){
printf("error fork\n");
return -1;
}
if(pid[i] == 0){
sem_wait(write_psem);
close(fd[0]);
scanf("%d", &number);
ret_len = write(fd[1], "A", number); //写入number个‘A’,默认阻塞型写,如果管道满则阻塞
printf("child %dth write length is: %d\n", i, ret_len);
sem_post(write_psem);
sem_post(read_psem[i]);
return 0; //子进程退出,父进程继续fork
}
}
//等待所有子进程结束
for(i = 0; i < CHILD_PROCESS_NUM; ++i)
sem_wait(read_psem[i]);
close(fd[1]);
ret_len = read(fd[0], buf, MAX_BUF);
close(fd[0]);
printf("last: read %dB\n", ret_len);
//信号量的彻底删除
sem_close(write_psem);
sem_unlink(SEM_NAME[3]);
for(i = 0; i < CHILD_PROCESS_NUM; ++i){
sem_unlink(SEM_NAME[i]); //read_psem已经为0,不需要sem_close()减1
}
return 0;
}
//问题二
//mypipe3.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#define FILE_NAME "./myfifo"
#define MAX_BUF 1024*8
int main()
{
int fd, ret_len, count, flags;
if(access(FILE_NAME,F_OK) != 0){ //如果文件存在
if(mkfifo(FILE_NAME, 0777) != 0){ //不存在就创建
printf("error create file");
return -1;
}
}
if( (fd = open(FILE_NAME, O_RDWR)) < 0){ //打开有名管道
printf("error open named pipe file");
return -1;
}
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); //设置非阻塞模式
count = 0;
while( (ret_len = write(fd, "A", MAX_BUF)) != -1){
count++;
printf("the %dth write %dB\n", count, ret_len);
}
printf("max of pipe length is %dKB\n", count*MAX_BUF/1024);
sleep(10);
return 0;
}
测试管道大小,就是不断往管道写数据直到写不进去,记录总共写了多少数据即可
这里将管道设置为非阻塞模式,所以当管道满的时候,返回值为-1。
//mypipe2.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#define FILE_NAME "./myfifo"
#define MAX_BUF 1024*8
char buf[MAX_BUF];
int count, ret_len, flags;
int main(int argc, char *argv[])
{
int fd;
if(access(FILE_NAME,F_OK) != 0){ //如果文件存在
if(mkfifo(FILE_NAME, 0777) != 0){ //不存在就创建
printf("error create file");
return -1;
}
}
if( (fd = open(FILE_NAME, O_RDWR)) < 0){ //打开有名管道
printf("error open named pipe file");
return -1;
}
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); //设置非阻塞模式
count = 0;
while( (ret_len = read(fd, "A", MAX_BUF)) != -1){ //读管道数据
count++;
printf("the %dth read %dB\n", count, ret_len);
}
return 0;
}
实例运行:
参考:
https://leslievan.github.io/2019/04/os-lab-3-2/#%E5%AE%9E%E9%AA%8C%E5%86%85%E5%AE%B9
3)消息队列+信号量
问题:基本要求+多进程同步+互斥通信
//自定义头文件
#ifndef EXP3_3_COMMON_H
#define EXP3_3_COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/msg.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <time.h>
#define QUEUE_ID 10086
#define MAX_SIZE 1024
#define MSG_STOP "exit"
#define snd_to_rcv1 1
#define snd_to_rcv2 2
#define rcv_to_snd1 3
#define rcv_to_snd2 4
#define CHECK(x) \
do { \
if (!(x)) { \
fprintf(stderr, "%s:%d: ", __func__, __LINE__); \
perror(#x); \
exit(-1); \
} \
} while (0) \
/* global variable */
sem_t *w_mutex, *empty, *full, *over, *rcv_dp, *snd_dp, *finish;
char *SEM_NAME[7] = {"w_mutex", "empty","full", "over", "rcv_dp", "snd_dp", "finish"};
void create_sem()
{
w_mutex = sem_open(SEM_NAME[0], O_CREAT, 0666, 1);
empty = sem_open(SEM_NAME[1], O_CREAT, 0666, 10);
full = sem_open(SEM_NAME[2], O_CREAT, 0666, 0);
over = sem_open(SEM_NAME[3], O_CREAT, 0666, 0);
rcv_dp = sem_open(SEM_NAME[4], O_CREAT, 0666, 0);
snd_dp = sem_open(SEM_NAME[5], O_CREAT, 0666, 1);
finish = sem_open(SEM_NAME[6], O_CREAT, 0666, 0);
return ;
}
void release_sem()
{
int i;
sem_close(w_mutex);
for(i = 0; i < 10; ++i)
sem_close(empty);
sem_close(snd_dp);
for(i = 0; i < 7; ++i)
sem_unlink(SEM_NAME[i]);
return ;
}
#define P(x) sem_wait(x)
#define V(x) sem_post(x)
struct msg_st {
long int message_type;
char buffer[MAX_SIZE];
};
//进程随机获得写数据的机会
int get_chance(int range)
{
srand((unsigned)time(NULL));
return rand()%range;
}
#endif //EXP3_3_COMMON_H
//sender1.c
#include "common.h"
int main(){
int mq;
struct msg_st buf;
ssize_t bytes_read;
//创建或者打开有名信号量
create_sem();
//创建消息队列
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
printf("sender1> ");
fflush(stdout);
fgets(buf.buffer, MAX_SIZE, stdin);
buf.message_type = snd_to_rcv1;
/* send the message */
P(empty);
P(w_mutex);
CHECK(0 <= msgsnd(mq, (void*)&buf, MAX_SIZE, 0));
V(full);
V(w_mutex);
} while (strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP)));
/* wait for response */
P(over);
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, rcv_to_snd1, 0);
CHECK(bytes_read >= 0);
printf("%s", buf.buffer);
V(finish);
return 0;
}
//sender2.c
#include "common.h"
int main(){
int mq;
struct msg_st buf;
ssize_t bytes_read;
//创建或者打开有名信号量
create_sem();
//创建消息队列
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
printf("sender2> ");
fflush(stdout);
fgets(buf.buffer, MAX_SIZE, stdin);
buf.message_type = snd_to_rcv2;
/* send the message */
P(empty); //
P(w_mutex);
CHECK(0 <= msgsnd(mq, (void*)&buf, MAX_SIZE, 0));
V(full);
V(w_mutex);
} while (strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP)));
/* wait for response */
P(over);
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, 0, 0);
CHECK(bytes_read >= 0);
printf("%s", buf.buffer);
V(finish);
return 0;
}
//receiver.c
#include "common.h"
int main()
{
create_sem();
struct msg_st buf, over1, over2;
int mq, must_stop = 2;
struct msqid_ds t;
over1.message_type = 3;
strcpy(over1.buffer, "over1\n");
over2.message_type = 4;
strcpy(over2.buffer, "over2\n");
/* open the mail queue */
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
ssize_t bytes_read, bytes_write;
/* receive the message */
P(full);
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, 0, 0);
V(empty);
CHECK(bytes_read >= 0);
if (!strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP))) {
if (buf.message_type == 1) {
printf("send over1 to sender1\n");
bytes_write = msgsnd(mq, (void *) &over1, MAX_SIZE, 0);
CHECK(bytes_write >= 0);
V(over);
must_stop--;
} else if (buf.message_type == 2) {
printf("send over2 to sender2\n");
bytes_write = msgsnd(mq, (void *) &over2, MAX_SIZE, 0);
CHECK(bytes_write >= 0);
V(over);
must_stop--;
}
} else {
printf("Received%ld: %s", buf.message_type, buf.buffer);
}
} while (must_stop);
P(finish);
P(finish);
CHECK(!msgctl(mq, IPC_RMID, &t));
release_sem();
return 0;
}
实例演示:
4)共享内存
同步和互斥关系
- sender和receiver需要互斥访问共享内存,用
sh_mutex
- receiver需要等待sender发送数据后,再读共享内存。存在同步关系,用
wait_sender
- sender发送消息后需要receiver发送应答消息后才能读共享空间。存在同步关系,用
wait_receiver
//自定义头文件
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <string.h>
#define MAX_SIZE 1024
#define SH_ADDRESS1 10000
#define SH_ADDRESS2 10001
struct shared_user_st
{
char text[MAX_SIZE];
};
#define CHECK(x) \
do { \
if (!(x)) { \
fprintf(stderr, "%s:%d: ", __func__, __LINE__); \
perror(#x); \
exit(-1); \
} \
} while (0) \
/*sem*/
#define P(x) sem_wait(x);
#define V(x) sem_post(x);
sem_t *wait_sender1, *wait_receiver1, *wait_sender2, *wait_receiver2;
char *SEM_NAME[4] = {"s1", "s2", "s3", "s4"};
void create_sem()
{
wait_sender1 = sem_open(SEM_NAME[0], O_CREAT, 0666, 0);
wait_sender2 = sem_open(SEM_NAME[2], O_CREAT, 0666, 0);
wait_receiver1 = sem_open(SEM_NAME[1], O_CREAT, 0666, 0);
wait_receiver2 = sem_open(SEM_NAME[3], O_CREAT, 0666, 0);
}
void release_sem()
{
int i;
for(i = 0; i < 4; ++i)
sem_unlink(SEM_NAME[i]);
}
#endif
//receiver.c
#include "common.h"
int main()
{
void *shm1 = NULL, *shm2 = NULL;
struct shared_user_st *shared1, *shared2 ; //point to shm
int shmid1, shmid2;
char buffer[MAX_SIZE];
create_sem();
shmid1 = shmget((key_t)SH_ADDRESS1, sizeof(struct shared_user_st), 0666|IPC_CREAT);
shmid2 = shmget((key_t)SH_ADDRESS2, sizeof(struct shared_user_st), 0666|IPC_CREAT);
CHECK(shmid1 != -1);
CHECK(shmid2 != -1);
int pid = fork();
if(pid == 0){
while(1){
P(wait_sender1); //父进程等待发送方的消息
shm1 = shmat(shmid1, NULL, 0);
CHECK(shm1 != (void *)-1);
shared1 = (struct shared_user_st *)shm1;
strncpy(buffer, shared1->text,MAX_SIZE);
printf("\nreceive:%s\n", buffer);
strcat(buffer, " over");
memcpy(shared1->text, buffer, sizeof(buffer));
shmdt(shm1); //分离出用户空间
V(wait_receiver2); //让2号进程知道已经有应答了
}
}
else if (pid > 0){
while(1){
shm2 = shmat(shmid2, NULL, 0);
CHECK(shm2 != (void *)-1);
shared2 = (struct shared_user_st *)shm2;
printf("please send:\n");
scanf("%s",buffer);
memcpy(shared2->text, buffer, sizeof(buffer));
V(wait_sender2);
P(wait_receiver1); //2号进程已经发出了应答
strcpy(buffer, shared2->text);
printf("\nreturn:%s\n", buffer);
shmdt(shm2);
}
}
else{
printf("fork error\n");
return -1;
}
release_sem();
return 0;
}
//sender.c
#include "common.h"
int main()
{
void *shm1 = NULL, *shm2 = NULL;
struct shared_user_st *shared1, *shared2 ; //point to shm
int shmid1, shmid2;
char buffer[MAX_SIZE];
create_sem();
shmid1 = shmget((key_t)SH_ADDRESS1, sizeof(struct shared_user_st), 0666|IPC_CREAT);
shmid2 = shmget((key_t)SH_ADDRESS2, sizeof(struct shared_user_st), 0666|IPC_CREAT);
CHECK(shmid1 != -1);
CHECK(shmid2 != -1);
int pid = fork();
if(pid == 0){
while(1){
P(wait_sender2); //父进程等待发送方的消息
shm2 = shmat(shmid2, NULL, 0);
CHECK(shm2!= (void *)-1);
shared2 = (struct shared_user_st *)shm2;
strncpy(buffer, shared2->text,MAX_SIZE);
printf("\nreceive:%s\n", buffer);
//scanf("%s", buffer);
strcat(buffer, " over");
memcpy(shared2->text, buffer, sizeof(buffer));
shmdt(shm2); //分离出用户空间
V(wait_receiver1); //让2号进程知道已经有应答了
}
}
else if (pid > 0){
while(1){
shm1 = shmat(shmid1, 0, 0);
CHECK(shm1 != (void *)-1);
shared1 = (struct shared_user_st *)shm1;
printf("please send:\n");
scanf("%s",buffer);
memcpy(shared1->text, buffer, sizeof(buffer));
V(wait_sender1);
P(wait_receiver2); //2号进程已经发出了应答
strcpy(buffer, shared1->text);
printf("\nreturn:%s\n", buffer);
shmdt(shm1);
}
}
else{
printf("fork error\n");
return -1;
}
release_sem();
return 0;
}
实例演示: