目录

Linux C语言系统编程作业

跟着别人简单学了下Linux下的系统编程,把做过的几个作业简单记录一下。作业的内容都是针对的具体的知识点,实现难度并不高。

进程间通信

设计一个程序,打开一个匿名管道,然后生成一个子进程,主进程向管道中写入信息“Hello XXX”,子进程从管道中读取数据,将读取到的信息在屏幕上显示。

主要考察forkpipe两个函数的使用,实现代码如下:

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

#define STRING "Hello wanakiki"
int main(){
    int pipefd[2];
    pid_t pid;
    char buf[BUFSIZ];

    if(pipe(pipefd) == -1){
        perror("pipe()");
        exit(1);
    }

    pid = fork();
    if(pid == -1){
        perror("fork()");
        exit(1);
    }

    if(pid == 0){
        // 子进程
        printf("Child pid is: %d\n", getpid());
        
        if(read(pipefd[0], buf, BUFSIZ) < 0){
            perror("write()");
            exit(1);
        }
        
        printf("%s\n", buf);
    }
    else{
        // 父进程
        printf("Parent pid is: %d\n", getpid());

        if (write(pipefd[1], STRING, strlen(STRING) + 1) < 0)
        {
            perror("write()");
            exit(1);
        }
        wait(NULL);
    }

    exit(0);
}

多进程

设计一个程序,运行后显示“开始计算”后,生成两个子进程。在主进程计算1+3+5+7+…+99的值,并将结果显示出来;在第一个子进程计算2+4+6+…+100的值,并将结果显示出来;第二个子进程中计算1+2+3+4+…+100的值,并将结果显示。

当需要创建的子进程数较少时,可以手动书写代码创建进程,在生成一个进程之后的父进程代码中再次调用fork生成进程。但是当需要创建的子进程较多时此种方法实现起来较为麻烦,可以用循环方式生成。本实验我采用了手动创建的方案,循环创建子进程的代码可以在最后综合实验中找到。

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

int main(){
    int p1, p2;
    int res;    // 保存计算结果
    int num = 0;
    while((p1 = fork()) == -1);     // 创建子进程

    if(p1 == 0){
        res = 0;
        for(int i = 2; i < 101; i++){
            res += i;
        }
        printf("第一个子进程计算结果 %d\n", res);
        printf("第一个子进程%d", ++num);
    }
    else{
        while((p2 = fork()) == -1);     // 创建子进程

        if(p2 == 0){
            res = 0;
            for(int i = 1; i < 101; i++){
                res += i;
            }
            printf("第二个子进程计算结果 %d\n", res);
            printf("第二个子进程%d", ++num);
        }
        else{
            res = 0;
            for(int i = 0; i < 100; i++){
                res += i;
            }
            printf("主进程计算结果 %d\n", res);
            printf("第三个子进程%d", ++num);
        }
    }
    printf("end");
}

信号

编写一个程序,接受发送给自己的信号(30和31),收到信号后显示信号的内容。没收到信号时循环显示提示信息。

程序给自己发送信号可以通过调用raise函数实现,在代码当中为了方便观察到发送信号的情况,对题目要求的两个信号的处理方法进行了设置。

#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void show_sig(int sig){
    printf("Get a signal: %d\n", sig);
}

int main(){
    signal(SIGPWR, show_sig);   // 设置信号处理方法
    signal(SIGSYS, show_sig);

    for(int i = 0; i < 8; i++){
        printf("系统将会在3秒和5秒发送30 31信号,当前时间为: %d\n", i);
        if(i == 3){
            raise(30);
        }
        if(i == 5){
            raise(31);
        }
        sleep(1);
    }
    return 0;
}

// https://blog.csdn.net/thanksgining/article/details/41824475

综合作业

有一个文件夹,下面有许多个C语言源程序。要求编写一个程序,统计所有C语言源程序中,下列系统函数的被调用次数。

printf  open  close  read  write  fork  signal

统计结果输出到myresult.txt文件按中,格式如下:

printf  49
open    13
close   13
read    24
write   16
fork     8
signal   0

建议(非强制,遵守下列建议会得到一个较好分数):

  1. 对每个.c文件,生成一个子进程(或者启动一个线程)统计,统计结果存入合适的文本文件中,最后再汇总结果
  2. 对每个函数,生成一个子进程(或者启动一个线程)统计,统计结果存入合适的文本文件中,最后再汇总结果。
  3. 访问文件使用Linux系统IO,如open, close, read, write。
  4. 编写出合适的makefile
  5. 可以生成多个可执行文件,通过一个主文件启动工作。
  6. 可以带Shell脚本

思路

程序的整体逻辑还是比较清楚的,读取文件夹内所有c文件,然后统计函数的调用次数,最后汇总到一个文件当中。如果不考虑多进程的话,实现起来并不复杂,但是题目要求使用多进程的方案,所以统计方式需要一定的变化。

我最开始的实现思路是对于每个C文件创建一个子进程来统计各个函数的使用次数,但实现的时候发现这样统计数据的保存不是很方便。如果每个文件都生成一个结果文件,当源文件数目较多时,生成的结果文件也会变得很多,汇总起来比较麻烦。如果是把每个文件的统计结果保存到一个文件当中,由于各个进程的执行时间不同,最后保存时还要进行额外的处理。

考虑到这些,我选择了一个比较简单的方案,对每个函数创建一个子进程,统计当前目录下所有源文件中该函数的调用次数,最后保存到结果文档当中,在程序结束之后用shell脚本对统计结果进行合并。

C程序:

#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h> // 文件目录
#include <string.h>

/*struct dirent
{
   long d_ino; // inode number 索引节点号
   off_t d_off; // offset to this dirent 在目录文件中的偏移
   unsigned short d_reclen; // length of this d_name 文件名长
   unsigned char d_type; // the type of d_name 文件类型
   char d_name [NAME_MAX+1]; // file name (null-terminated) 文件名,最长255字符
}
其中d_type表明该文件的类型:文件(8)、目录(4)、链接文件(10)等
*/

// 文件操作相关 https://blog.csdn.net/u014650722/article/details/51563679
char *aims[] = {"printf", "open", "close", "read", "write", "fork", "signal"};


int helper(char *filename, int id)
{
    int fd = open(filename, O_RDONLY); // 只读模式打开文件
    char buf[1024];
    int count = 0;
    while (read(fd, buf, 1024))
    {
        // strstr函数判断是否为子串
        char * tmp = strstr(buf, aims[id]);
        while(tmp != NULL)
        {
            count++;
            tmp += strlen(aims[id]);    //找到后必须移动,不然死循环
            tmp = strstr(tmp, aims[id]);
        }
    }
    close(fd);
    return count;
}

void savefile(int id, int count){
    // 保存结果
    char buf[1024];
    char *savename = (char *)malloc(strlen(aims[id]) + 4);
    sprintf(savename, "%s.txt", aims[id]);

    int fd = open(savename, O_CREAT | O_RDWR, S_IWUSR);
    int len = sprintf(buf, "%s %d\n", aims[id], count);
    write(fd, buf, len);
    close(fd);
}

int main()
{
    int i = 0;
    int p = 0;
    for(i = 0; i < 7; i++){
        p = fork();
        if(p == 0){
            break;
        }
    }

    if (p == 0)
    {
       
        // 获取目录下所有.c文件
        DIR *directory_pointer;
        struct dirent *entry;

        if ((directory_pointer = opendir(".")) == NULL)
        {
            printf("Error open\n");
            return 0;
        }
        else
        {
            int count = 0;
            while ((entry = readdir(directory_pointer)) != NULL)
            {
                int len = strlen(entry->d_name);
                if (entry->d_name[len - 1] == 'c' && entry->d_name[len - 2] == '.')
                {
                    count += helper(entry->d_name, i);
                }
            }
            savefile(i, count);
        }
    }
    else
    {
        return  0;   //主进程不做处理
    }
}

shell脚本:

#!/bin/bash

funname=("printf" "open" "close" "read" "write" "fork" "signal")

gcc final.c -o final    # c文件名为final.c
./final
rm final    # 运行结束后删除

rm res.txt  # 删除原有结果文本

for fun in ${funname[@]}
do
    cat $fun.txt >> res.txt
    rm $fun.txt
done