几个linux实验

环境

  Ubuntu18.04.3(desktop-amd64)4核70G、Linux 5.4.1(最新)、Deepin15.11(Ubuntu用起来不习惯)、draw.io、VSCode

Part1

目的

  添加系统调用,扩展操作系统的功能。

①基本配置

  ①先下载源码,我是从主机上下载后传到虚拟机上的,放到/usr/src文件夹中。下载链接

  ②解压,注意权限要切换成root。

  tar -xavf linux-5.4.1.tar.xz

  ③依赖包,注意安装前更新一下apt,最好换一下源,碰到错误缺什么装什么。

  apt-get install libncurses5-dev
  apt-get install libssl-dev
  apt-get install bison
  apt-get install flex
  apt-get install make

②添加系统调用号

  vim arch/x86/entry/syscalls/syscall_64.tbl

  

③添加系统调用服务例程原型声明

  vim include/linux/syscalls.h 

  

④添加系统调用服务例程

  vim kernel/sys.c

  

⑤配置内核

    make menuconfig

  图忘记截了,一般采用默认值就行,Save与Exit看到就选。

⑥编译内核

  这里可以事先装一个ccache。(它保存了gcc的输出信息,等到下一次编译时有更新才会编译)

  ccache包链接: https://pan.baidu.com/s/1laa5jO_ngp2HVgbMU__kNA 提取码: byjf 

  tar -xvf ccache-3.6.tar.xz
   cd ccache-3.6
  ./configure -prefix=/var/ccache

  然后编译内核。(-j4的4是内核数)

    make -j4

  10min左右就编完了,有了ccache下次编1min不到。

⑦编译模块

    make modules

⑧安装内核等

  安装模块:

  make modules_install

  安装内核:

  make install

  配置grub引导程序:

  update-grub2

  重启:

  reboot

⑨测试

  启动后,在/usr/src添加一个测试文件。

  demo1.c

  

  用uname -a查看内核版本,开始测试。 

  

相关源码

find_get_pid

  解释:根据进程号pid_t nr nr得到进程描述符struct pid ,并将进程描述符中的字段count+1。

struct pid *find_get_pid(pid_t nr)
{
    struct pid *pid;
    rcu_read_lock();
    pid = get_pid(find_vpid(nr));
    rcu_read_unlock();
    return pid;
}

pid_task

  解释:获取任务的任务描述符信息,此任务在进程pid的使用链表中,并且搜索的链表的起始元素的下标为参数type的值。

struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
    struct task_struct *result = NULL;
    if (pid) {
        struct hlist_node *first;
        first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
                          lockdep_tasklist_lock_is_held());
        if (first)
            result = hlist_entry(first, struct task_struct, pid_links[(type)]);
    }
    return result;
}

copy_to_user

  解释:从目标地址/用户空间将拷贝数据拷贝到源地址/内核空间,成功返回0,失败返回没有拷贝成功的数据字节数。

static __always_inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
    if (likely(check_copy_size(from, n, true)))
        n = _copy_to_user(to, from, n);
    return n;
}

task_nice

  解释:用于获取当前task的nice值,并返回nice值。

static inline int task_nice(const struct task_struct *p)
{
    return PRIO_TO_NICE((p)->static_prio);
}

set_user_nice

  解释:用于设置进程的nice值。

void set_user_nice(struct task_struct *p, long nice)
{
    bool queued, running;
    int old_prio, delta;
    struct rq_flags rf;
    struct rq *rq;

    if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)
        return;
    /*
     * We have to be careful, if called from sys_setpriority(),
     * the task might be in the middle of scheduling on another CPU.
     */
    rq = task_rq_lock(p, &rf);
    update_rq_clock(rq);

    /*
     * The RT priorities are set via sched_setscheduler(), but we still
     * allow the 'normal' nice value to be set - but as expected
     * it wont have any effect on scheduling until the task is
     * SCHED_DEADLINE, SCHED_FIFO or SCHED_RR:
     */
    if (task_has_dl_policy(p) || task_has_rt_policy(p)) {
        p->static_prio = NICE_TO_PRIO(nice);
        goto out_unlock;
    }
    queued = task_on_rq_queued(p);
    running = task_current(rq, p);
    if (queued)
        dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
    if (running)
        put_prev_task(rq, p);

    p->static_prio = NICE_TO_PRIO(nice);
    set_load_weight(p, true);
    old_prio = p->prio;
    p->prio = effective_prio(p);
    delta = p->prio - old_prio;

    if (queued) {
        enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);
        /*
         * If the task increased its priority or is running and
         * lowered its priority, then reschedule its CPU:
         */
        if (delta < 0 || (delta > 0 && task_running(rq, p)))
            resched_curr(rq);
    }
    if (running)
        set_curr_task(rq, p);
out_unlock:
    task_rq_unlock(rq, p, &rf);
}

Part2

目的

  ①设计一个不带参数的模块,能列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID

  ②设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号进程状态

不带参模块

  第一部分是设计一个列出所有内核线程的程序名的不带参模块show_all_kernel_thread.c,这个需要通过利用内核进程的总链表实现,每个进程通过take_struct结构的next_task、prev_task成员加入总链表。

  

  Makefile

  

  模块编译

  make

  加载模块

  insmod show_all_kernel_thread.ko

  查看已加载模块

  lsmod

  

  查看某一模块是否加载可以用lsmod|grep 模块名

  

  模块载入后,其中的init函数运行,将内核线程的一些信息打印到日志中。

  dmesg查看系统日志。

  

  New Terminal,ps -aux显示所有内核线程。

  

  可以看出是对应的。

  卸载模块

  rmmod 模块名

  

带参模块

  第二部分是设计一个以某个进程的PID号为参数的,能列出这个进程的父进程、子进程和兄弟进程的模块 show_task_family。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/pid.h>
#include <linux/list.h>
#include <linux/sched.h>
MODULE_LICENSE("GPL");
static int pid;
module_param(pid, int, 0644);
static int __init show_task_family_init(void)
{
    struct pid *ppid;
    struct task_struct *p;
    struct task_struct *pos;
    char *ptype[4] = {"[I]", "[P]", "[S]", "[C]"};
    // 通过进程的PID号pid一步步找到进程的进程控制块p
    ppid = find_get_pid(pid);
    if (ppid == NULL){
        printk("[ShowTaskFamily] Error, PID not exists.\n");
        return -1;
    }
    p = pid_task(ppid, PIDTYPE_PID);
    // 格式化输出表头
    printk("%-10s%-20s%-6s%-6s\n", "Type", "Name", "PID", "State");// Itself
    // 打印自身信息
    printk("%-10s%-20s%-6d%-6d\n", ptype[0], p->comm, p->pid, p->state);
    // Parent
    // 打印父进程信息
    printk("%-10s%-20s%-6d%-6d\n", ptype[1], p->real_parent->comm,
           p->real_parent->pid, p->real_parent->state);
    // Siblings
    // 遍历父进程的子,即我的兄弟进程,输出信息
    // “我”同样是父进程的子进程,所以当二者进程PID号一致时,跳过不输出
    list_for_each_entry(pos, &(p->real_parent->children), sibling){
        if (pos->pid == pid)
            continue;
        printk("%-10s%-20s%-6d%-6d\n", ptype[2], pos->comm, pos->pid,
               pos->state);
    }
    // Children
    // 遍历”我“的子进程,输出信息
    list_for_each_entry(pos, &(p->children), sibling){
        printk("%-10s%-20s%-6d%-6d\n", ptype[3], pos->comm, pos->pid,
               pos->state);
    }
    return 0;
}
static void __exit show_task_family_exit(void){
    printk("[ShowTaskFamily] Module Uninstalled.\n");
}
module_init(show_task_family_init);
module_exit(show_task_family_exit);

  Makefile

obj-m := show_task_family.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
    make -C $(KDIR) M=$(PWD) modules
clean:
    make -C $(KDIR) M=$(PWD) clean

  以相同的方式载入,这里注意需要输入一个pid参数。

  查看进程树。

pstree -p

  以pid为1293为例。

  

  其中I表示自己进程,P表示父进程,C表示兄弟进程,S表示子进程。

   

相关源码

for_each_process

  参考自Linux-4.1.2/include/linux/sched/signal.h

#define for_each_process(p) \
    for (p = &init_task ; (p = next_task(p)) != &init_task ; )

  通过它可以扫描整个进程链表。

list_for_each_entry

  参考自Linux-4.1.2/include/linux/list.h

/*
    struct task_struct *pos;
    list_for_each_entry(pos, &pos->children, sibling);
*/

#define list_for_each_entry(pos, head, member)                \
    for (pos = __container_of((head)->next, pos, member);        \
     &pos->member != (head);                    \
     pos = __container_of(pos->member.next, pos, member))

  其中,container_of()来自Linux-4.1.2/include/linux/kernel.h,计算返回包含ptr指向的成员member所在的type类型数据结构的指针。

#define container_of(ptr, type, member) ({            \
        const typeof(((type *)0)->member) * __mptr = (ptr);    \
        (type *)((char *)__mptr - offsetof(type, member)); })

  list_for_each_entry将一个叫做list_head的数据结构放在结构体中,就可以使结构体具有链表的功能。图片来源:https://my.oschina.net/u/3857782/blog/1849617/

  list_head一方面通过自身的prev和next指针,构成一个链表,这部分是粘钩的钩子,另一方面,通过寄生在其他的结构体中,就像粘钩带粘性的那一端,将结构体粘住。这样作为宿主的结构体也在链表中了。

  

part3

目的

  ①实现一个模拟shell

  编写三个不同的程序cmd1.c、cmd2.c、cmd3.c,每个程序功能自定,分别编译成可执行文件cmd1、cmd2、cmd3。

  然后再编写一个程序,模拟shell程序的功能:能根据用户输入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程的结束,然后再等待接收下一条命令。

  如果接收到的命令为exit,则父进程结束,退出模拟shell,如果接收到无效命令,则显示“Command not found”,继续等待下一条命令。

  ②实现一个管道通信程序

  由父进程创建一个管道,然后再创建三个子进程,并由这三个子进程用管道与父进程之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息

  通信的具体内容可根据自己的需要随意设计,要求能够实验阻塞型读写过程的各种情况,测试管道的默认大小,并要求利用Posix信号量机制实现进程间对管道的互斥访问

  运行程序,观察各种情况下,进程实际读写的字节数以及进程阻塞唤醒情况。

  ③利用linux的消息队列通信机制实现两个线程间的通信

  编写程序创建三个线程:sender1线程、sender2线程、receiver线程,三个线程的功能描述如下:

  a、sender1:运行函数sender1(),它创建一个消息队列,然后等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不在发送消息,最后向receiver线程发送消息“end1”,并且等待receiver的应答,等到应答消息后,将接收到的应答消息显示在终端屏幕上,结束线程的运行。

  b、sender2:运行函数sender2(),共享sender1创建的消息队列,等待用户通过终端输入一串字符,并且将这串字符通过消息队列发送给receiver线程;

  可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end2”,并且等待receiver的应答,等到应答消息后,将接收到的信息显示在终端屏幕上,结束线程的运行。

  c、receiver:运行函数receiver(),它通过消息队列接收来自sender1和sender2两个线程的消息,将消息显示在终端屏幕上,当收到的内容为“end1”的消息时,就向sender1发送一个应答消息“over1”;

  当收到内容为“end2”的消息时,就向sender2发送一个应答消息“over2”;

  消息接收完成后删除消息队列,结束线程的运行。选择合适的信号量机制实现三个线程之间的同步与互斥。

  ④利用Linux的共享内存通信机制实现两个进程间的通信

  编写程序sender,它创建一个共享内存,然后等待用户通过终端输入一串字符,并将这串字符通过共享内存发送给receiver,最后,它等待receiver的应答,收到应答消息后,它接收到的应答消息显示在终端屏幕上,删除共享内存,结束程序运行。

  编写receiver程序,它通过共享内存接收来自sender的消息,将消息显示在终端屏幕上,然后再通过该共享内存向sender发送一个应答消息”over”,结束程序的运行。使用合适的信号量机制实现两个进程对共享内存的互斥及同步使用。

模拟Shell

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define CMD_COLLECTION_LEN 4 //命令数组的长度(有哪几个命令)

//command index
#define INVALID_COMMAND    -1 //无效命令返回-1
#define EXIT    0
#define CMD_1 1
#define CMD_2 2
#define CMD_3 3

char *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 INVALID_COMMAND;
}


/*
创建子进程,这里使用了execl,后面的l表示list,即参数列表。
第一参数为path(要执行的文件路径),最后一个参数必须是NULL,
中间的为要传送的参数
*/
void myFork(int cmdIndex)
{
    pid_t pid;    //即 int

    if((pid = fork())<0){
        printf("Fork subprocess failure!\n");
         exit(0);
    }
    else if (pid == 0){
        int execl_status = -1;    //初始化为-1

        printf("\nSubprocess working...\n");

        switch(cmdIndex){
            case CMD_1:
                execl_status = execl("./cmd1","cmd1",NULL);    //函数执行不成功返回-1
                break;
            case CMD_2:
                execl_status = execl("./cmd2","cmd2",NULL);
                break;
            case CMD_3:
                execl_status = execl("./cmd3","cmd3",NULL);
                break;
            default:
                printf("ERROR! Command NOT FOUND!\n");
                break;
        }
        
        // 返回值为-1则表明出错
         if(execl_status<0){
            printf("Executing execl() ERROR!\n");
            exit(0);
        }
        else{
            printf("Process complete!\n");
            exit(0);
        }
    }
    else{
        return;
    }
}


//运行cmd
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[30]; //命令数组(最长30)
    int cmdIndex; //用于显示运行哪个数据

    while(1){
        printf("\nPlease input command\n>>:");
        scanf("%s",cmdStr);
        cmdIndex = getCmdIndex(cmdStr);
        runCMD(cmdIndex);
        wait(0);
    }
}

 

管道通信

思路:

  无名管道用于具有的亲缘关系的进程,父子兄弟进程。

  在阻塞方式下,若设备不可读写,则该进程休眠,释放CPU资源;若设备文件可读写,则对设备文件进行读写。

  在非阻塞方式下,若设备不可读写,进程放弃读写,继续向下执行;若设备文件可读写,则对设备文件进行读写。

  简而言之: 非阻塞读写要加入判断语句(在管道满无法继续写入时返回-EAGAIN,作为循环终止条件);

  而阻塞型读写在指定读写量后自行终止。

  阻塞方式,使用'Posix信号量'机制辅以实现对管道的读写使用;非阻塞方式使用'循环+判断'机制实现读写的终止。

#include <errno.h>
#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
#define CHECK(x)                                            \
    do {                                                    \
        if (!(x)) {                                         \
            fprintf(stderr, "%s:%d: ", __func__, __LINE__); \
            perror(#x);                                     \
            exit(-1);                                       \
        }                                                   \
    } while (0)

/**
    无名管道用于具有的亲缘关系的进程,父子兄弟进程。
    
    在阻塞方式下,若设备不可读写,则该进程休眠,释放CPU资源;若设备文件可读写,则对设备文件进行读写。
    在非阻塞方式下,若设备不可读写,进程放弃读写,继续向下执行;若设备文件可读写,则对设备文件进行读写。

    简而言之:  非阻塞读写要加入判断语句(在管道满无法继续写入时返回-EAGAIN,作为循环终止条件);
                而阻塞型读写在指定读写量后自行终止。

    阻塞方式,使用'Posix信号量'机制辅以实现对管道的读写使用;非阻塞方式使用'循环+判断'机制实现读写的终止
*/
 
int main(int argc, char **argv) {
    int pipefd[2], pid, i = 0;    //0读,1写
    int flag = 0;    //文件状态标志
    ssize_t n;    // aka. long int
    char buf[BUF_MAX_SIZE];//用作write的缓冲区,保存写入的字符
    char str[BUF_MAX_SIZE];//用作read的缓冲区,保存读取的字符

    //创建有名信号量,若不存在则创建,若存在则直接打开,默认值为0
    sem_t *write_mutex;//限制了父进程先读取数据,然后子进程二、三写入数据
    sem_t *read_mutex1;//限制了子进程2 写入数据 2,然后父进程读取数据 2
    sem_t *read_mutex2;//限制了子进程3 写入数据 3,然后父进程读取数据 3
    
    write_mutex = sem_open("pipe_test_wm", O_CREAT | O_RDWR, 0666, 0);//创建|读写方式打开,权限位,初始值=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);

    //字符置换函数,内存空间初始化0
    memset(buf, 0, BUF_MAX_SIZE);
    memset(str, 0, BUF_MAX_SIZE);

    // 创建匿名管道并检查操作是否成功
    CHECK(pipe(pipefd) >= 0);

    // 创建第一个子进程并检查操作是否成功
    CHECK((pid = fork()) >= 0);

    // 第一个子进程,利用非阻塞写测试管道大小
    if (pid == 0) {
        int count = 0;
        close(pipefd[0]);//close reading
    //F_GETFL获取文件状态标志
        int flags = fcntl(pipefd[1], F_GETFL);//FCNTL()控制已打开文件的的各种属性

        // 新建的匿名管道默认是阻塞写,通过`fcntl`设置成非阻塞写,在管道满无法继续写入时返回-EAGAIN,作为循环终止条件
        fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);//F_SETFL设置文件状态标志,非阻塞写

        // 写入管道
        printf("\n>>Child1 (非阻塞写):\n");
        while (!flag) {
    //从缓冲器中向pipefd[1]的偏移量后写入buf中的BUF_MAX_SIZE字节,直到无法继续写入
    //成功则返回写入的字节数,若出错则返回-1
            n = write(pipefd[1], buf, BUF_MAX_SIZE);
            if (n == -1) {
                flag = 1;
            } else {
                count++;
                printf("write %ld B to pipe\n", n);
            }
        }
        printf("\nDefault pipe size = %d KB\n", (count * BUF_MAX_SIZE) / 1024);//打印8times,64kb.
        exit(0);
    }

    // 创建第二个子进程并检查操作是否成功
    CHECK((pid = fork()) >= 0);
    if (pid == 0) {
        sem_wait(write_mutex);//父进程先读子进程一,子进程二再写
        close(pipefd[0]);//关闭管道读端
        n = write(pipefd[1], "This is the second child.\n", 28);//子进程二、三均为阻塞性写
        printf("\n>>Child 2 (阻塞写):\nwrite %ld B to pipe\n", n);
        sem_post(write_mutex);//子进程二写完成
        sem_post(read_mutex1);//父进程读进程二
        exit(0);
    }

    // 创建第三个子进程并检查操作是否成功
    CHECK((pid = fork()) >= 0);
    if (pid == 0) {
        sem_wait(write_mutex);//请求管道write
        close(pipefd[0]);
        n = write(pipefd[1], "This is the third child.\n", 18);
        printf("\n>>Child 3 (阻塞写):\nwrite %ld B to pipe\n", n);
        sem_post(write_mutex);//子进程三写完成
        sem_post(read_mutex2);//父进程读进程三
        exit(0);
    }

    //父进程必须接收到子进程结束之后返回的 0,才能继续运行,否则阻塞。
    //读取子进程一写入的数据,否则子进程二、三无法继续写入.
    //读空管道后结束循环,释放信号量,子进程二、三继续运行。
    wait(0);
    close(pipefd[1]);//关闭管道wirte端
    int flags = fcntl(pipefd[0], F_GETFL);//F_GETFL获取文件状态标志;出错返回-1

    // 设置非阻塞性读,直到不能再读入,作为循环结束标志
    fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK);
    
    printf("\n>>Father (非阻塞读):\n");
    while (!flag) {
        n = read(pipefd[0], str, BUF_MAX_SIZE);
        if (n == -1) {
            flag = 1;
        } else {
            printf("read %ld B from pipe\n", n);
        }
    }
    
    fcntl(pipefd[0], F_SETFL, flags | ~O_NONBLOCK);// 设置阻塞性读,作为循环结束标志
    printf("\n>>Father (阻塞读):\n");
    
    fcntl(pipefd[1], F_SETFL, flags | ~O_NONBLOCK);// 设置阻塞性写
    sem_post(write_mutex);//子进程二、三继续写入
    
    
    // 等待子进程二、三写入完毕,输出子进程二三写入的数据
    sem_wait(read_mutex1);
    sem_wait(read_mutex2);
    n = read(pipefd[0], str, BUF_MAX_SIZE);//返回实际读取的数据大小
    printf("\n>>Father:\nread %ld B from pipe (actual size)\n\nThe contents are:\n", n);
    for (i = 0; i < n; i++) {
        printf("%c", str[i]);
    }
    printf("\n\n注:以上读写字节数均为'实际读写字节数'\n");

    //关闭信号量
    sem_close(write_mutex);
    sem_close(read_mutex1);
    sem_close(read_mutex2);
    //系统中删除有名信号量
    sem_unlink("pipe_test_wm");
    sem_unlink("pipe_test_rm_1");
    sem_unlink("pipe_test_rm_2");
    return 0;
}

消息队列

思路:

  本次实验采用了3个semaphore信号量,但是我认为完全可以采用flag机制更好。

  (下面的共享内存是用的flag机制,这样做更加便于理解,直接设置3个信号量)

  过一段时间自己写的是啥都忘了...重新看一遍会耗费时间

/*
    本次实验采用了3个semaphore信号量,但是我认为完全可以采用flag机制
    (可参考实验3-4对flag机制使用) 这样做更加便于理解,直接设置3个信号量
    过一段时间自己写的是啥都忘了...重新看一遍会耗费时间
*/
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define TRUE 1

#define BUF_SIZE 128    // maxium message size
#define KEY_NUM 0    // assign key number

//int f = 0;    // count finished senders (f==2: all threads abort) ; aka. 'flag', which has been utilized in Receiver
int sender_id = 0;    // [Improvement] if Sender1 sent 'end2', Sender2 would not be aborted
int msgid;    // message id

// message buffer struct; in order to simulate msgbuf in Linux, we added attribute 'mtype', actually it is unnecessary in this project
typedef struct msgbuf
{
    long int mtype;        // unsigned long type,>0, receive function used it to confirm message type 
    char mtext[BUF_SIZE];    // 128,message content
}msgbuf;

sem_t full;        // whether message queue is full  (whether available for receiver)
sem_t empty;    // whether message queue is empty (whether available for sender)
sem_t mutex;    // whether message queue available

key_t key; // only match one queue


// Receiver
void * Receiver(void *arg)
{
    msgbuf msg;
    msg.mtype = 1;    // define mtype as type "1"; it is unnecessary, could be eliminated
    
    int flag=0;        // judge if it reached ends
    
    while(TRUE)
    {
        sem_wait(&full);    // wait for message queue full (available for Receiver)
        sem_wait(&mutex);    // wait for message queue available
        
        msgrcv(msgid,&msg,sizeof(msgbuf),1,0);        // mtype=1, 0: ignore (to control if no appointed mtype mathched); Function Prototype: int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg)
        printf("\n\n>>Receiver: %s\n",msg.mtext);
        
        if(strcmp(msg.mtext,"end1") == 0 && sender_id == 1) // [Improvement]
        {
            msg.mtype = 2;    // change mtype to 2, abort Sender1
            strncpy(msg.mtext,"over1",BUF_SIZE);    // aka. msg->mtext = over1, yet C does not confirm this writing style
            msgsnd(msgid,&msg,sizeof(msgbuf),0);    // fourth parameter: 0->ignore zone bit
            sem_post(&mutex);
            flag++;        // Sender1 ended
        }
        else if(strcmp(msg.mtext,"end2") == 0 && sender_id == 2)
        {
            msg.mtype = 3;    // change mtype to 3, abort Sender2
            strncpy(msg.mtext,"over2",BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&mutex);
            flag++;        // Sender2 ended
        }
        else{
            sem_post(&empty);    // message queue empty (Receiver has received message)
            sem_post(&mutex);    // message queue available
        }
        
        // exit
        if(flag == 2){
            sleep(1);
            sem_destroy(&full);
            sem_destroy(&empty);
            sem_destroy(&mutex);
            exit(EXIT_SUCCESS);
        }
    }
}

// Sender1
void * Sender1(void *arg)
{
    char str[BUF_SIZE];
    int flag = 0;    // [Improvement] after 'exit',flag=1,which means no longer send message
    msgbuf msg;
    
    while (TRUE)
    {
        msg.mtype = 1;
        
        sem_wait(&empty);    // wait for message queue empty (available for Sender)
        sem_wait(&mutex);    // wait for message queue available
        
        if(flag == 0){
            printf("\n\n>>Sender1\nPlease input the message you want to send:\n");
            scanf("%s", str);
            while(getchar()!='\n');
        }
        else{
            while(1){
                printf("\n\n>>Sender1\nFrom now on, you could not enter message but 'end1'\n");
                scanf("%s", str);
                while(getchar()!='\n');
                if( strcmp(str,"end1") == 0 )
                    break;
            }
        }
        
        if( strcmp(str,"end1") == 0 && flag == 1) {
            sender_id = 1;    // being utilized in the improvement part
            strncpy(msg.mtext,str,BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&full);
            sem_post(&mutex);
            sleep(0.5);
            sem_wait(&mutex);
            msgrcv(msgid,&msg,sizeof(msgbuf),2,0);
            printf("\nFrom Receiver: %s\n", msg.mtext);
            sem_post(&empty);
            sem_post(&mutex);
            break;
        }
        else if(strcmp(str,"exit") == 0 && flag == 0){
            printf("Sender1 exit\n");
            flag = 1;    // no longer send message
            sem_post(&empty);    // Sender2 is available
            sem_post(&mutex);
            sleep(1);    // Switch to Sender2
        }
        else if(strcmp(str,"end1") != 0 && flag == 0){
            sender_id = 1;    // being utilized in the improvement part
            strncpy(msg.mtext,str,BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&full);    // message queue full (message has been sent to the queue)
            sem_post(&mutex);    // message queue available
        }
        else{
            printf("\nPlease input 'exit' before entering 'end1' or 'end2'\n");
            sem_post(&empty);
            sem_post(&mutex);
        }
    }
    
}

// Sender2
void * Sender2(void *arg)
{
    char str[BUF_SIZE];
    int flag = 0;
    msgbuf msg;
    
    while (TRUE)
    {
        msg.mtype = 1;
        
        sem_wait(&empty);    // wait for message queue empty (available for Sender)
        sem_wait(&mutex);    // wait for message queue available
        
        if(flag == 0){
            printf("\n\n>>Sender2\nPlease input the message you want to send:\n");
            scanf("%s", str);
            while(getchar()!='\n');
        }
        else{
            while(1){
                printf("\n\n>>Sender2\nFrom now on, you could not enter message but 'end2'\n");
                scanf("%s", str);
                while(getchar()!='\n');
                if( strcmp(str,"end2") == 0 )
                    break;
            }
        }
        
        if( strcmp(str,"end2") == 0 && flag == 1) {
            sender_id = 2;    // being utilized in the improvement part
            strncpy(msg.mtext,str,BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&full);
            sem_post(&mutex);
            sleep(0.5);
            sem_wait(&mutex);
            msgrcv(msgid,&msg,sizeof(msgbuf),3,0);
            printf("\nFrom Receiver: %s\n", msg.mtext);
            sem_post(&empty);
            sem_post(&mutex);
            break;
        }
        else if(strcmp(str,"exit") == 0 && flag == 0){
            printf("Sender2 exit\n");
            flag = 1;    // no longer send message
            sem_post(&empty);    // Sender1 is available
            sem_post(&mutex);
            sleep(1);    // Switch to Sender1
        }
        else if(strcmp(str,"end2") != 0 && flag == 0){
            sender_id = 2;    // being utilized in the improvement part
            strncpy(msg.mtext,str,BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&full);    // message queue full (message has been sent to the queue)
            sem_post(&mutex);    // message queue available
        }
        else{
            printf("\nPlease input 'exit' before entering 'end1' or 'end2'\n");
            sem_post(&empty);
            sem_post(&mutex);
        }
    }
    
}


int main()
{
    pthread_t sender_pid;
    pthread_t receiver_pid;

    sem_init(&full,0,0);    // first 0: multi-thread synchronization; second 0: initial value
    sem_init(&empty,0,1);
    sem_init(&mutex,0,1);

    key = KEY_NUM;    // assign 'key' value
    
    // first parameter: match the key of message queue; second parameter: user read/ write/ create if not exist
    if((msgid = msgget(key, S_IRUSR | S_IWUSR | IPC_CREAT)) == -1)
    {
        printf("Create Message Queue Error!\n");
        exit(EXIT_FAILURE);        // abnormal exit
    }
    else{
        printf("Create Message Queue Complete!\n\n");
    }
    
    pthread_create(&sender_pid,NULL,Sender1,NULL);    // second parameter: attributes (prio/size...), default is NULL;    
    pthread_create(&sender_pid,NULL,Sender2,NULL);    // third parameter: execute function (indicator function)
    pthread_create(&receiver_pid,NULL,Receiver,NULL);    // fourth parameter: thread execution parameter
    
    pthread_join(sender_pid,NULL);    // waiting for the thread end
    pthread_join(receiver_pid,NULL);
    
    msgctl(msgid,IPC_RMID,NULL);
    printf("\n\nMain Function End...\n");

    return 0;
}

 

共享内存

  我认为信号量机制的实现不必要引用系统提供的semaphore,

  可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制;

  这样做有比直接使用semaphore更为改进之处:

  1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观;

  2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试

  (虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件)

Sender

/*
    经过实验三中第三个小实验的经历,我认为信号量机制的实现不必要引用系统提供的semaphore,
    可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制;
    
    这样做有比直接使用semaphore更为改进之处:
    1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观;
    2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试
    (虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件)
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>
 
void *pAddr;
int shmId;

//共享内存
struct Msg{
    int flag;    // 0:Read; 1:Write; 2:Cease
    char content[32];
};

// Sender
void main(){
    key_t key = ftok(".",2);
    //key_t key = 123456;    //也可以直接给出key值,只要保证和Sender中的一样即可
    shmId = shmget(key,128,IPC_CREAT | IPC_EXCL | 0666);
    pAddr = shmat(shmId,0,0);
    if(*(int *)pAddr == -1){
        printf("shmat error!\n"); 
        exit(0);
    }

    struct Msg * msg = (struct Msg *)pAddr;
    memset(pAddr,0,128);
    msg->flag = 1; //只写
    
    while(1){
        
        if(msg->flag == 1){
            printf("\nFrom Receiver:\n");
            printf("%s",msg->content); //这里改为gets()等函数可以实现语句的输入,并在后续传输整个句子,不局限于一个单词
            printf("\n\n>>Sender\nPlease input contents:\n");
            scanf("%s", msg->content);
            msg->flag = 0; //只读
        }
        
        else if(msg->flag == 2){ //strcmp(msg->content,"over") == 0
            printf("\n>>Sender\nFrom Receiver: over!\n");
            shmdt(pAddr);
            shmctl(shmId,IPC_RMID,0);
            exit(0);
        }
    }

}

Receiver

/*
    经过实验三中第三个小实验的经历,我认为信号量机制的实现不必要引用系统提供的semaphore,
    可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制;
    
    这样做有比直接使用semaphore更为改进之处:
    1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观;
    2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试
    (虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件)
*/


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <string.h>

void *pAddr; //新建指针,用以指向当前进程地址
int shmId; //共享内存ID
 
struct Msg
{
    int flag;    // 0:Read; 1:Write; 2:Cease
    char content[32];
};


// Server
void main()
{

    key_t key = ftok(".",2);    //把当前文件路径名和一个整数标识符转换成IPC的key值,以保证和clint中的共享内存一致;第二个参数:计划代号
    //key_t key = 123456;    //也可以直接给出key值,只要保证和Sender中的一样即可
    shmId = shmget(key,0,0);    //如果没有该块共享内存,则创建、并返回共享内存ID;若已有该块共享内存,则返回-1
    
    pAddr = shmat(shmId,0,0);    //共享内存连接到当前进程的地址空间
    if(*(int *)pAddr == -1)
    {
        printf("shmat error!\n"); 
        exit(0);
    }

    struct Msg *msg = (struct Msg *)pAddr;
    
    while(1)
    {
        if(msg->flag == 0)    //只读
        {
            printf("\n\n>>Receiver\n\nFrom sender: %s\n", msg->content);
            printf("Please response (you may enter 'over' to abort the process):\n");
            scanf("%s", msg->content);
            if(strcmp(msg->content,"over") == 0){
                msg->flag = 2;    //通知Sender共享内存结束
                exit(0);
            }
            else{
                msg->flag = 1; //进程未结束,继续从Sender获取讯息
            }
        }
    }
    
}

 

参考链接

  https://blog.csdn.net/babybabyup/article/details/79720082
  https://blog.csdn.net/zyf2333/article/details/80043152
  https://www.linuxidc.com/Linux/2016-04/129955.htm
  https://www.jianshu.com/p/60d2b4f86159
  http://www.voidcn.com/article/p-tmnvznnb-bmp.html
  https://blog.csdn.net/xiao_jj_jj/article/details/82755954
  https://blog.csdn.net/zxhio/article/details/80312316
  https://blog.csdn.net/w13635739860/article/details/99288899
  https://zhuanlan.zhihu.com/p/87420486

  部分链接的内容存在bug,仅参考的一部分。

猜你喜欢

转载自www.cnblogs.com/fangxiaoqi/p/11777380.html