Linux系统编程入门

Linux系统编程入门

1、软件安装

  • vm

    VMware Workstation16 密钥:
    
      ZF3R0-FHED2-M80TY-8QYGC-NPKYF
    
      YF390-0HF8P-M81RQ-2DXQE-M2UT6
    
      ZF71R-DMX85-08DQY-8YMNC-PPHV8
    
  • boa_ubuntu.zip 配置好环境的Ubuntu虚拟机文件

  • sublime 编辑器

2、Linux基础

2.1 设置

  • 终端字体调节

    • 放大:Ctrl Shift +

    • 缩小:Ctrl -

  • 输入法切换

    • 英->中:Ctrl 空格
    • 中->英:Shift、Ctrl Shift、Ctrl 空格
  • [~]

    • :当前用户目录 为/home/用户名
    • 根目录(/) 家目录(/home) 用户目录(~)

2.2 基础命令

  • pwd :查看当前目录的绝对路径
  • ls :查看当前路径下的内容
  • cd :切换目录
  • mkdir :创建目录
  • touch :创建文件
  • cp :复制
  • mv :剪切
  • rm :删除
  • clear :清屏

2.3 终端使用技巧

  • tab
    • 对齐
  • 上下
    • 查看历史命令
  • 左右
    • 移动光标更改命令

3、C语言编译运行

# 通过gedit编辑器创建C语言源文件
gedit a.c   
  • 源文件
#include <stdio.h>

int main()
{
    printf("hello world!");
    return 0;
}
  • 编译
    • 使用编译器将编写正确的代码生成机器能够识别的机器码(二进制)
# 编译器为gcc,默认生成a.out,此处通过-o选项指定文件名为a
# 一步完成
gcc a.c -o a	

# 分步完成
预处理:gcc -E a.c -o a.i
编译:	gcc -S a.i -o a.s
汇编:	gcc -c a.s -o a.o
链接:	gcc a.o -o a
  • 运行

# 运行
./a

Example:打字游戏

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>   //tcgetattr函数所需头文件
#include <unistd.h>

/*
select_mode() 选择游戏难度
*/
int select_mode()
{
	int n = 0;
	printf("请选择游戏难度:\n");
	printf("1:简单,2:中等,3:困难, 4:自定义\n");
	scanf("%d", &n);
	if( n < 1 || n > 4 )
		printf("难度选择错误!\n");
	return n;
}

/*
show_rule() 输出游戏规则
*/
void show_rule()
{	
	printf("游戏规则如下:\n");
	printf("1、按下任意键开始游戏\n");
	printf("2、按下首字母开始计时\n");
	printf("3、正确输入则输出本身,错误输入则输出?\n");
	printf("\n");
}

/*
get_str() 获取键盘按下的按键	
*/
char get_str()
{
	struct termios oldt,newt;
	char ch;    
	tcgetattr(STDIN_FILENO,&oldt);
	newt=oldt;
	newt.c_lflag &=~( ICANON | ECHO);
	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
	ch = getchar();
	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
	return ch;
}

/*
game_start() 获取,判断,回显,结束输出

参数:	str 随机字符串
	n 字符串长度
*/
void game_start(char* str, int n)
{
	char ch;
	int count = 0;
	int start_time, end_time;
	int i;
	for(i =0; i < n; i++)
	{
 		ch = get_str();
		if(ch == str[i])
		{
			count++;
			printf("%c", ch);
		}
		else
			printf("?");

		if(0 == i)
			start_time = time(NULL);
	}
	end_time = time(NULL);
	printf("\n");
	printf("共用时%d\n", end_time - start_time);
	printf("正确率为%.2f%%\n", count * 1.0 / n * 100);
}

/* 函数声明 */
void play_again();

/*
create_str() 创建用来输入的随机字符串

参数:	n 选择的难度等级
*/
void create_str(int n)
{
	int num = 0;

	/* 根据选择的不同难度设置随机字符串长度 */
	switch(n)
    {
		case 1:{ num = 10; break; }
		case 2:{ num = 20; break; }
		case 3:{ num = 30; break; }
		case 4:{ printf("请输入一个数:"); scanf("%d", &num); break; }
	}
	char* word = malloc(num + 1);	/* 还需要给结束标志位('\0')分配内存 */
	memset(word, 0, num);			/* 清空数组内容 */

	int i;
	srand(time(NULL));
	for(i = 0; i < num; i++)
	{
		word[i] = rand() % 26 + 'a';
	}
	get_str();			/* 此函数会阻塞进程 */
	system("clear");	/* 终端清屏 */	
	puts(word);
	game_start(word, num);
	free(word);
	play_again();	
}

/*
play_again() 可重新选择难度开始游戏,也可以结束游戏
*/
void play_again()
{
	char ch;
	printf("\n按下任意键结束游戏,按下Esc重新开始游戏\n");
	ch = get_str();
	if(ch == 0x1B)
	{
		system("clear");
		int n = select_mode();
		create_str(n);
	}
	else
		printf("欢迎下次游玩!\n");
}

int main()
{
	show_rule();
	int n = select_mode();
	create_str(n);
	return 0;	
}

4、Linux系统编程

实现多任务:

  • 程序:静态的、文件
  • 进程:动态的、程序的执行实例
    • 每个进程都由一个进程号来标识,其类型为pid_t,进程号的范围:0~32767。
    • 进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用了。
#查看当前进程
ps -e

4.1 获取进程号

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

int main()
{
	/* 定义进程号 */
	pid_t pid, ppid, pgid;

	/* 获取当前进程进程号 */
	pid = getpid();
    printf("当前进程号:%d\n", pid);
    
    ppid = getppid();
    printf("当前进程父进程号:%d\n", ppid);
    
    pgid = getpgid(pid);
    printf("当前进程进程组号:%d\n", pgid);
    
    while(1)
    {
     	
    }
}

4.2 创建进程

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

int main()
{
	/* 定义进程号 */
	pid_t pid;

	/* 创建进程 */
	pid = fork();
	if(pid < 0)		/* 返回-1:创建失败 */
	{
		perror("fork");	/* 输出错误原因 */
		return 0;		/* 结束程序 */
	}
	else if(0 ==pid)	/* 返回0:创建成功,子进程 */
	{
		while(1)
		{
			printf("子进程\n");
			sleep(1);
		}
	}
	else			/* 值为子进程进程号:main,父进程 */
	{	
		while(1)
		{
			printf("父进程\n");
			sleep(1);
		}
	}
}

4.3 创建多个进程

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

int main()
{
    int i;
    for(i = 0; i < 2; i++)
    {
		pid_t pid = fork();

		if(pid < 0)			/* 返回-1:创建失败 */
        {
            perror("fork");	/* 输出错误原因 */
            return 0;		/* 结束程序 */
        }
        else if(0 ==pid)	/* 返回0:创建成功,子进程 */
        {
            break;
        }
    }
	if(0 == i)		/* 第一个子进程 */
    {
    	printf("第一个子进程\n"); 
    }
    else if(1 == i) /* 第二个子进程 */
    {
		printf("第二个子进程\n");
    }
	else
    {
    	printf("父进程\n");
    }
}

4.4 fork函数特点:

  • 父子进程是交替轮循进行的
  • fork创建的进程,谁先开始不一定,cpu先给谁分配工作时间,谁就先开始
  • 进程创建子进程的时候,会将原本具有的资源,复制一份给子进程父子进程具有独立的地址空间
  • 子进程要比他的父进程先结束,父进程要负责回收子进程的资源如果父进程比他的子进程先结束,那么这个子进程就会变成孤儿进程他会被1 (init)收养,会回收他的资源
三种孤儿进程
  • 孤儿进程
  • 僵尸进程
  • 精灵进程(守护进程)
验证fork函数第三个特点
#include <stdio.h>
#include <unistd.h>

int b = 9;

int main()
{
    int a = 9;
    /* 定义进程号 */
	pid_t pid;

	/* 创建进程 */
	pid = fork();
	if(pid < 0)		/* 返回-1:创建失败 */
	{
		perror("fork");	/* 输出错误原因 */
		return 0;		/* 结束程序 */
	}
	else if(0 ==pid)	/* 返回0:创建成功,子进程 */
	{
        a++;
        b++;
        printf("son: a=%d, b=%d\n", a, b);
	}
	else			/* 值为子进程进程号:main,父进程 */
	{	
        sleep(1);
		printf("father: a=%d, %d\n", a, b);
	}
}
验证fork函数第四个特点
#include <stdio.h>
#include <unistd.h>

int b = 9;

int main()
{
    int a = 9;
    /* 定义进程号 */
	pid_t pid;

	/* 创建进程 */
	pid = fork();
	if(pid < 0)		/* 返回-1:创建失败 */
	{
		perror("fork");	/* 输出错误原因 */
		return 0;		/* 结束程序 */
	}
	else if(0 ==pid)	/* 返回0:创建成功,子进程 */
	{
        int i;
		for(i = 0; i < 5; i++)
		{
            a++;
            b++;
			printf("son: a=%d, b=%d\n", a, b);
            sleep(1);
		}
	}
	else			/* 值为子进程进程号:main,父进程 */
	{	
		printf("father: a=%d, %d\n", a, b);
	}
}

4.5 vfork函数特点:

  • 一定是子进程先运行,父进程处于挂起的状态直到 子进程调用了exit、exec函数,父进程才会开始
  • 子进程会在子进程调用了exit、exec函数的时候复制父进程资源
验证vfork函数特点
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int b = 9;

int main()
{
    int a = 9;
    /* 定义进程号 */
	pid_t pid;

	/* 创建进程 */
	pid = vfork();
	if(pid < 0)		/* 返回-1:创建失败 */
	{
		perror("vfork");	/* 输出错误原因 */
		return 0;		/* 结束程序 */
	}
	else if(0 ==pid)	/* 返回0:创建成功,子进程 */
	{
		int i;
		for(i = 0; i < 5; i++)
		{
            	a++;
            	b++;
				printf("son: a=%d, b=%d\n", a, b);
            	sleep(1);
		}
		exit(0);
	}
	else			/* 值为子进程进程号:main,父进程 */
	{	
		sleep(2);
		printf("father: a=%d, %d\n", a, b);
	}
}

对比:

  • fork
    • 数据不变,说明从一开始父子进程就具有独立的物理空间
    • 子进程成为孤儿进程,说明父子进程交替进行
  • vfork
    • 数据改变,说明子进程调用函数之前,父子进程共用地址空间,父进程再次启动才会复制资源
    • 子进程没有成为孤儿进程,说明父进程再次启动需要触发条件

5、进程间通信

5.1 无名管道

pipe ,只能实现有亲缘关系的进程间通信

  • 变量,有两个文件描述符
  • 半双工:数据在某一时刻只能单向传递
  • 写端写,读端读
  • FIFO
子进程写,父进程读
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char sendbuf[256]="";	/* 发送缓存区 */
char recvbuf[256]="";	/* 接受缓存区 */

int main()
{
    int fd[2];	/* 创建整型变量 */
    pipe(fd);	/* fd[0]:读端 \ fd[1]:写端 */
	/* 定义进程号 */
	pid_t pid;

	/* 创建进程 */
	pid = fork();
	if(pid < 0)		/* 返回-1:创建失败 */
	{
		perror("fork");	/* 输出错误原因 */
		return 0;		/* 结束程序 */
	}
	else if(0 ==pid)	/* 返回0:创建成功,子进程 */
	{
		printf("请输入需要发送的内容:\n");
        scanf("%s", sendbuf);
        write(fd[1], sendbuf, sizeof(sendbuf));
        /* 写 文件标志位 写缓存区 写缓存区内容多长 */
        close(fd[0]);
        close(fd[1]);
	}
	else			/* 值为子进程进程号:main,父进程 */
	{	
		read(fd[0], recvbuf, sizeof(recvbuf));	/* 会阻塞进程 */
        /* 读 文件标志位 读缓存区 读缓存区内容多长 */
        printf("father receive: %s\n", recvbuf);
        close(fd[0]);
        close(fd[1]);
	}
}

5.2 有名管道

fifo ,可以实现没有亲缘关系的进程间通信

  • 文件:打开、读、写、关闭

    /* 创建有名管道 */
    mkfifo("路径/管道名", 权限);
    
    /* 当前目录下创建管理员可读可写可执行,其他人可读可写的有名管道 */
    mkfifo("a_fifo", 0766);
    
    /* 上级目录下创建所有人可读可写的有名管道 */
    mkfifo("../a_fifo", 0666);
    
写进程
#include <stdio.h>
#include <stdlib.h>

int main()
{
	mkfifo("a_fifo", 0666);
    int fd = open("a_fifo", O_WRONLY);	/* O_WRONLY:只写 */
    
    char msg[32]= "";
    printf("请输入需要发送的内容:\n");
    scanf("%[^\n]", msg);		/* [^\n]:正则表达式,即读入换行符就结束输入 */   
    write(fd, msg, sizeof(msg));
    
    close(fd);
}
读进程
#include <stdio.h>
#include <stdlib.h>

int main()
{
	mkfifo("a_fifo", 0666);
    int fd = open("a_fifo", O_RDONLY);	/* O_RDONLY:只读 */
    
    char msg[32]= "";
    read(fd, msg, sizeof(msg));
    printf("收到消息:%s\n", msg);
    
    close(fd);
}
理论上
  • 不需要再写创建管道的语句,读写使用同根管道,但要保证,写创建管道语句的一边要先运行
实际上
  • 为了避免运行顺序导致的读写失败,就会在两边都加上创建管道的语句,但要保证,两边创建、打开的管道名字和路径相同

Example:Jack and Rose

Jack
#include <stdio.h> 
#include <fcntl.h> 
#include <string.h> 
#include <stdlib.h> 
 
int main(int argc, char const *argv[]) 
{ 
    int i; 
    for (i = 0; i < 2; ++i) 
    { 
        pid_t pid = fork(); 
        if (pid < 0) 
        { 
            perror("fork"); 
            return 0; 
        } 
        else if (pid == 0) 
        { 
            break; 
        }} 
    if (i == 0)  		/* 发送消息的子进程 */   
    { 
        mkfifo("jtor",0666); 
        int fd = open("jtor",O_RDWR); 
        while(1) 
        { 
            printf("jack:\n"); 
            char msg[32]=""; 
            scanf("%[^\n]",msg); 
            scanf("%*c"); 			/* 跳过输入的一个字符 */
            write(fd,msg,sizeof(msg)); 
            if (strcmp(msg,"bye")==0) 
            { 
                break; 
            } 
        } 
        close(fd); 

    } 
    else if (i == 1)	/* 接受消息的子进程 */
    { 
        mkfifo("rtoj",0666); 
        int fd = open("rotj",O_RDWR); 
        while(1) 
        { 
            char msg[32]=""; 
            read(fd,msg,sizeof(msg)); 
            printf("rose说:%s\n",msg); 
            if (strcmp(msg,"bye")==0) 
            { 
                break; 
            } 
        } 
        close(fd); 
    } 
    else 
    { 

        while(1) 
        { 
            pid_t pid  = waitpid(-1,NULL,WNOHANG); 
            if (pid  > 0)   	/* 刚结束的进程进程号 */
            { 
                printf("进程%d结束啦\n",pid); 
            } 
            else if (pid == 0)  /* 还有未结束的进程 */
            { 
                continue; 
            } 
            else if(pid == -1)  /* 进程全部结束 */
            { 
                break; 
            } 
        } 

    } 
    return 0; 
}
Rose
#include <stdio.h> 
#include <fcntl.h> 
#include <string.h> 
#include <stdlib.h> 
int main(int argc, char const *argv[]) 
{ 
    int i; 
    for (i = 0; i < 2; ++i) 
    { 
        pid_t pid = fork(); 
        if (pid < 0) 
        { 
            perror("fork"); 
            return 0; 
        } 
        else if (pid == 0) 
        { 
            break; 
        } 
    } 
	if (i == 0)			/* 发送消息的子进程 */  
    { 
        mkfifo("rotj",0666); 
        int fd = open("rotj",O_RDWR); 
        while(1) 
        { 
            printf("rose:\n"); 
            char msg[32]=""; 
            scanf("%[^\n]",msg); 
            scanf("%*c"); 			/* 跳过输入的一个字符 */
            write(fd,msg,sizeof(msg)); 
            if (strcmp(msg,"bye")==0) 
            { 
                break; 
            } 
        } 
        close(fd); 

    } 
    else if (i == 1)	/* 接受消息的子进程 */ 
    { 
        mkfifo("jtor",0666); 
        int fd = open("jtor",O_RDWR); 
        while(1) 
        { 
            char msg[32]=""; 
            read(fd,msg,sizeof(msg)); 
            printf("jack说:%s\n",msg); 
            if (strcmp(msg,"bye")==0) 
            { 
                break; 
            } 
        } 
        close(fd); 
    } 
    else 
    { 

        while(1) 
        { 
            pid_t pid  = waitpid(-1,NULL,WNOHANG); 
            if (pid  > 0)   	/* 刚结束的进程进程号 */ 
            { 
                printf("进程%d结束啦\n",pid); 
            } 
            else if (pid == 0)  /* 还有未结束的进程 */ 
            { 
                continue; 
            } 
            else if(pid == -1)  /* 进程全部结束 */ 
            { 
                break; 
            } 
        } 

    } 
    return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值