几个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,仅参考的一部分。

原文地址:https://www.cnblogs.com/fangxiaoqi/p/11777380.html

时间: 2024-10-03 00:01:22

几个linux实验的相关文章

linux实验三:ShellShock 攻击实验

ShellShock 攻击实验 20125113 赵恺 一. 实验描述 2014年9月24日,Bash中发现了一个严重漏洞shellshock,该漏洞可用于许多系统,并且既可以远程也可以在本地触发.在本实验中,学生需要亲手重现攻击来理解该漏洞,并回答一些问题. 二. 预备知识 1. 什么是ShellShock? Shellshock,又称Bashdoor,是在Unix中广泛使用的Bash shell中的一个安全漏洞,首次于2014年9月24日公开.许多互联网守护进程,如网页服务器,使用bash来

用VMwareWorkstation搭建linux 实验

搭建32位的linux实验 选择好硬件的兼容性  要不然安装的硬件不匹配 选择好LINUX 32位 虚拟的LINUX 存放的位置 LINUX设置2048MB的内存 选择仅主机模式,这样以后好方便做实验 下面选择默认的就行了 给LINUX的磁盘60个G 要不然 LINUX玩不起了 LINUX磁盘文件存放的位置 设置LINUX的镜像文件 考虑到有的人英文不好 就安装中文版的吧 设置输入法 清除储存设备里面可能含有的数据 设置LINUX在网络上的主机名 设置离本地最近的城市 设置LINUX的 登录密码

Linux实验报告-构建一个LVS的DR模型

Linux实验报告-构建一个LVS的DR模型 实验背景: 学习笔记,构建一个LVS的DR模型 实验目的: 了解LVS的DR模型工作原理 实现一个LVS的DR模型的实验 实验环境: Vmware Workstation 9,CentOS 6.4  实验步骤: 1,实验目的规划如下模型,CIP.VIP.DIP与RIP在同一网段 2.RS1上配置如下: 配置内核参数: #echo 1 > /prco/sys/net/ipv4/conf/lo/arp_ignore                    

Linux实验一

一.Linux 简介 实验介绍 本节主要介绍 Linux 的历史,Linux 与 Windows 的区别等入门知识.如果你已经有过充分的了解,可以跳过本节,直接进入下一个实验. 一.Linux 为何物 Linux 就是一个操作系统,就像你多少已经了解的 Windows(xp,7,8)和 Max OS ,至于操作系统是什么,就不用过多解释了,如果你学习过前面的入门课程,应该会有个基本概念了,这里简单介绍下操作系统在整个计算机系统中的角色. 我们的 Linux 也就是系统调用和内核那两层,当然直观的

Linux实验二报告

北京电子科技学院(BESTI) 实     验    报     告 课程:信息安全系统设计基础                     班级: 201352 姓名:池彬宁 贺邦 学号:20135212 20135208 成绩:             指导教师:   娄嘉鹏                     实验日期:2015.11.17 实验密级:         预习程度:                               实验时间:15:30~18:20 仪器组次:  

Linux实验——缓冲区溢出漏洞实验

一.     实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写. 二.     实验准备 实验楼提供的是64位Ubuntu linux(系统用户名shiyanlou,密码shiyanlou),而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.输入命令安装一些用于编译32位C程序的东

Linux实验二

一        第一个实验 Linux基础 1 通过娄老师关于分析学霸学渣的前言 明白了真正的学习一门功课应该是思考本质 而不是纯属记忆 2 全部的命令如下 Linux命令格式:command [options] arguments man命令 man man man-k cheat命令 (不是Linux自带的命令,使用其可以作弊) 其他命令 find locate grep whirese which 二     第二个实验 Linux下C语言编程基础 vim :文本编辑器 大写K: 查找函

Linux实验四报告

张文俊 + 原创作品转载请注明出处+ <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.学习内容 系统调用:操作系统为用户态进程与硬件设备进行交互提供了一组接口 API:应用编程接口,是一个函数定义. 操作系统提供API和系统调用的关系. Libc库定义的一些API引用封装例程(wrapper routine).一般每个系统调用对应一个封装例程.库再用这些封装例程定义给用户的API. 返回值: 1.大部分封装

操作系统是如何工作的--------Linux 实验二

操作系统是如何工作的? 作者:20135108 李泽源 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 mykernel实验指导(操作系统是如何工作的) 运行并分析一个精简的操作系统内核,理解操作系统是如何工作的 使用实验楼的虚拟机打开shell cd LinuxKernel/linux-3.9.4 qemu -kernel arch/x86/boot/bzImage 然后cd mykernel 您可以看到qe

linux实验二:SET-UID程序漏洞实验

SET-UID程序漏洞实验 20125113 赵恺 一.实验描述 Set-UID 是Unix系统中的一个重要的安全机制.当一个Set-UID程序运行的时候,它被假设为具有拥有者的权限.例如,如果程序的拥有者是root,那么任何人运行这个程序时都会获得程序拥有者的权限.Set-UID允许我们做许多很有趣的事情,但是不幸的是,它也是很多坏事情的罪魁祸首. 因此本次实验的目标有两点: 1.欣赏好的方面,理解为什么Set-UID是需要的,以及它是如何被执行的. 2.注意坏的方面,理解它潜在的安全性问题.