Linux 系统编程

快捷键

用命令:

文件和目录:

cd /home 进入 ‘/home’ 目录

cd … 返回上一级目录

cd …/… 返回上两级目录

cd - 返回上次所在目录

cp file1 file2 将file1复制为file2

cp -a dir1 dir2 复制一个目录

cp -a /tmp/dir1 . 复制一个目录到当前工作目录(.代表当前目录)

ls 查看目录中的文件

ls -a 显示隐藏文件

ls -l 显示详细信息

ls -lrt 按时间显示文件(l表示详细列表,r表示反向排序,t表示按时间排序)

pwd 显示工作路径

mkdir dir1 创建 ‘dir1’ 目录

mkdir dir1 dir2 同时创建两个目录

mkdir -p /tmp/dir1/dir2 创建一个目录树

mv dir1 dir2 移动/重命名一个目录

rm -f file1 删除 ‘file1’

rm -rf dir1 删除 ‘dir1’ 目录及其子目录内容

查看文件内容:

cat file1 从第一个字节开始正向查看文件的内容

head -2 file1 查看一个文件的前两行

more file1 查看一个长文件的内容

tac file1 从最后一行开始反向查看一个文件的内容

tail -3 file1 查看一个文件的最后三行

文本处理:

grep str /tmp/test 在文件 ‘/tmp/test’ 中查找 “str”

grep ^str /tmp/test 在文件 ‘/tmp/test’ 中查找以 “str” 开始的行

grep [0-9] /tmp/test 查找 ‘/tmp/test’ 文件中所有包含数字的行

grep str -r /tmp/* 在目录 ‘/tmp’ 及其子目录中查找 “str”

diff file1 file2 找出两个文件的不同处

sdiff file1 file2 以对比的方式显示两个文件的不同

查找:

find / -name file1 从 ‘/’ 开始进入根文件系统查找文件和目录

find / -user user1 查找属于用户 ‘user1’ 的文件和目录

find /home/user1 -name *.bin 在目录 ‘/ home/user1’ 中查找以 ‘.bin’ 结尾的文件

find /usr/bin -type f -atime +100 查找在过去100天内未被使用过的执行文件

find /usr/bin -type f -mtime -10 查找在10天内被创建或者修改过的文件

locate *.ps 寻找以 ‘.ps’ 结尾的文件,先运行 ‘updatedb’ 命令

find -name ‘*.[ch]’ | xargs grep -E ‘expr’ 在当前目录及其子目录所有.c和.h文件中查找 ‘expr’

find -type f -print0 | xargs -r0 grep -F ‘expr’ 在当前目录及其子目录的常规文件中查找 ‘expr’

find -maxdepth 1 -type f | xargs grep -F ‘expr’ 在当前目录中查找 ‘expr’

压缩和解压:

bzip2 file1 压缩 file1

bunzip2 file1.bz2 解压 file1.bz2

gzip file1 压缩 file1

gzip -9 file1 最大程度压缩 file1

gunzip file1.gz 解压 file1.gz

tar -cvf archive.tar file1 把file1打包成 archive.tar

(-c: 建立压缩档案;-v: 显示所有过程;-f: 使用档案名字,是必须的,是最后一个参数)

tar -cvf archive.tar file1 dir1 把 file1,dir1 打包成 archive.tar

tar -tf archive.tar 显示一个包中的内容

tar -xvf archive.tar 释放一个包

tar -xvf archive.tar -C /tmp 把压缩包释放到 /tmp目录下

zip file1.zip file1 创建一个zip格式的压缩包

zip -r file1.zip file1 dir1 把文件和目录压缩成一个zip格式的压缩包

unzip file1.zip 解压一个zip格式的压缩包到当前目录

unzip test.zip -d /tmp/ 解压一个zip格式的压缩包到 /tmp 目录

yum工具:

yum -y install [package] 下载并安装一个rpm包

yum localinstall [package.rpm] 安装一个rpm包,使用你自己的软件仓库解决所有依赖关系

yum -y update 更新当前系统中安装的所有rpm包

yum update [package] 更新一个rpm包

yum remove [package] 删除一个rpm包

yum list 列出当前系统中安装的所有包

yum search [package] 在rpm仓库中搜寻软件包

yum clean [package] 清除缓存目录(/var/cache/yum)下的软件包

yum clean headers 删除所有头文件

yum clean all 删除所有缓存的包和头文件

网络:

ifconfig eth0 显示一个以太网卡的配置

ifconfig eth0 192.168.1.1 netmask 255.255.255.0 配置网卡的IP地址

ifdown eth0 禁用 ‘eth0’ 网络设备

ifup eth0 启用 ‘eth0’ 网络设备

iwconfig eth1 显示一个无线网卡的配置

iwlist scan 显示无线网络

ip addr show 显示网卡的IP地址

其他:

su - 切换到root权限(与su有区别)

shutdown -h now 关机

shutdown -r now 重启

top 罗列使用CPU资源最多的linux任务 (输入q退出)

pstree 以树状图显示程序

man ping 查看参考手册(例如ping 命令)

passwd 修改密码

df -h 显示磁盘的使用情况

cal -3 显示前一个月,当前月以及下一个月的月历

cal 10 1988 显示指定月,年的月历

date --date ‘1970-01-01 UTC 1427888888 seconds’ 把一相对于1970-01-01 00:00的秒数转换成时间

常用快捷键:

CentOS 6.4 中可以通过系统->首选项->键盘快捷键来设置快捷键,如图所示。例如可将运行终端的快捷键设为Ctrl+Alt+T。

Ctrl + u 删除光标之前到行首的字符

Ctrl + k 删除光标之前到行尾的字符

Ctrl + c 取消当前行输入的命令,相当于Ctrl + Break

Ctrl + a 光标移动到行首(ahead of line),相当于通常的Home键

Ctrl + e 光标移动到行尾(end of line)

Ctrl + f 光标向前(forward)移动一个字符位置

Ctrl + b 光标往回(backward)移动一个字符位置

Ctrl + l 清屏,相当于执行clear命令

Ctrl + r 显示:号提示,根据用户输入查找相关历史命令(reverse-i-search)

Ctrl + w 删除从光标位置前到当前所处单词(word)的开头

Ctrl + t 交换光标位置前的两个字符

Ctrl + y 粘贴最后一次被删除的单词

Ctrl + Alt + d 显示桌面

Alt + b 光标往回(backward)移动到前一个单词

Alt + d 删除从光标位置到当前所处单词的末尾

Alt + F2 运行

Alt + F4 关闭当前窗口

Alt + F9 最小化当前窗口

Alt + F10 最大化当前窗口

Alt + Tab 切换窗口

Alt +按住左键 移动窗口(或在最下面的任务栏滚动鼠标滑轮)

[鼠标中间键] 粘贴突出显示的文本。使用鼠标左键来选择文本。把光标指向想粘贴文本的地方。点击鼠标中间键来粘贴。

[Tab] 命令行自动补全。使用 shell 提示时可使用这一方式。键入命令或文件名的前几个字符,然后按 [Tab] 键,它会自动补全命令或显示匹配键入字符的所有命令。

在桌面或文件管理器中直接按 / 就可以输入位置,打开文件管理器。

快速搜索:在 vi 或 Firefox 中直接按 / 即可进入搜索状态。

网站链接和图片可直接拖放到桌面或者目录,可以马上下载。

直接将文件管理器中的文件拖到终端中就可以在终端中得到完整的路径名。

原文链接:https://blog.csdn.net/weixin_33196106/article/details/113584767

Linux C函数库

Linux 菜鸟教程

一、在Linux下的C语言开发Linux 系统编程_第1张图片

1、基本命令

(1)Ctal + Alt + t 调出命令窗口;
(2)输入 vi a.c 存在a.c文件则打开,不存在则创建
vimdiff a.c b.c 在窗口同时打开
(3)进入C语言开发模式(默认是命令行状态),按下 i 输入模式(insert字行消失), Esc 返回命令行模式
(4)写完之后输入“ :wq ” //就是保存并退出文件,w:保存 q:退出
(5)接着就是编译 .c 文件,gcc a.c -o FirstProject // a.c 是要编译的文件 FirstProject 是生成的程序名字
(6)运行程序: ./ + 程序名字 例 : ./ FirstProject

2、常用命令

(1)ls (显示当前文件夹下的所有文件) ls -a (显示所有文件,包括隐藏的文件和文件夹)
(2)pwd (显示当前文件路径,当前在哪个路径)
(3)mkdir (创建文件夹 例: mkdir one 就是生成名字为 one 的文件夹 )
(4)cd (就是打开文件夹 例: cd one 就是打开当前路径下的oen文件夹)
(5)cd …cd (就是返回上一文件夹)
(6)tab键 就是自动补全 如果文件或者文件名太长按下tab键会快速补全
(7)mv 有移动和重命名的功能 (移动:mv pp new pp就是当前文件夹下叫pp的文件移动到new文件夹下,其中new与pp必须在同一文件夹下) (重命名 mv a.c b.c 就是把a.c 变成b.c )
(8)cp ( 拷贝指令) 例: cp b.c test.c 把b.c复制一份在当前文件夹下名字是 test.c
(9)rm 删除指令 rm a.c 就是删除文件a.c
(10)exit 是退出当前命令窗口指令
(11)xrandr 调出分辨率窗口, 改变窗口大小 xrandr -s 1400 * 900
(12)cd /mnt/hgfs/Share/ 就是访问win10系统和linux系统共同分享的文件夹
(13)在编辑状态下, :set number 作用是显示字行数**

二、文件

1、文件的打开和创建操作

open函数的官方解释:
Linux 系统编程_第2张图片
Linux 系统编程_第3张图片

文件打开代码例程:

代码说明:
如果当文件下没有file1文件的情况会创建file1文件,如果存在则直接退出程序

#include 
#include 
#include 
#include 

int main()
{
    int fd = 0; // open 函数会返回一个int类型的变量,用fd变量来保存
    fd = open("./file1", O_RDWR);   // "./file1" 表示当前文件夹下的 file1 文件,O_RDWR表示可读可写
    if (fd == -1)   // 当文件不存在时返回-1
    {
        printf("open file1 failed\n");
        // O_RDWR 代表可读可写
        fd = open("./file1", O_RDWR | O_CREAT, 0600);   // 如果不存在 O_CREAT 的作用就是创建 file1 文件, 0600 代表可读可写权限
        if (fd > 0) // 文件存在返回正数
        {
            printf("create file1 success!\n");
        }
    }

    return 0;
}

open函数的用法

#include 
#include 
#include 
#include 

int main()
{
    int fd;

/**
 * 如果file1文件不存在则会创建file1文件,如果存在open函数会返回-1
 * 
 **/
    fd = open("./file1", O_RDWR | O_CREAT | O_EXCL, 0x600);
    if(fd == -1)                    // O_EXCL 文件存在则返回-1
    {
        printf("file exist!!!\n");
    }
    else
    {
        printf("file creat succeed!!!\n");
    }

    return 0;
}

在文件末尾添加数据,不改变原本内容!!!

#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main()
{
    int fd;
    char *buf = "welcom to Linux !!!";

    /* 
    	O_RDWR 是从文件的开头开始写入,并覆盖原有的内容
		O_APPEND 在文件末尾处写入,不改变原有内容
		O_TRUNC 在清除文件的数据,并头开始写入
	*/

    fd = open("./file1", O_RDWR | O_APPEND);		
    printf("open file success!!!\n");

    int n_write = write(fd, buf, strlen(buf));
    if(n_write != -1)
    {
        printf("write %d byte to file\n", n_write);
    }
    close(fd);
    return 0;
}

创建文件:
Linux 系统编程_第4张图片
Linux 系统编程_第5张图片

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
	int fd;
	char *buf = "Hello Linux World";

//	int creat(const char *pathname, mode_t mode);
	fd = creat("./file2", S_IRWXU);     // 创建文件,权限是可读可写可执行
   /************************************** 
    *常见创建模式(mode):                 vi 
	* 宏表示		数字		              
	* S_IRUSR   4        可读                
	* S_IWUSR   2		 可写             
	* S_IXUSR   1        可执行
	* S_IRWXU   7        可读可写可执行
	**/
	
	write(fd, buf, strlen(buf));        // 写入文件

	return 0;
}

2、文件的写入操作

#include 
#include 
#include 
#include 
#include 		// 调用write()和close()函数
#include 		// 调用strlen() 函数

/*
 *     int open(const char *pathname, int flags);
 *     int open(const char *pathname, int flags, mode_t mode);
 *     int creat(const char *pathname, mode_t mode);
 * */

int main()
{
    int fd = 0;
    char *buf = "Welcom to Linux"; // 写入file1中的内容
    fd = open("./file1", O_RDWR);
    if (fd == -1)
    {
        printf("open file1 failed\n");
        fd = open("./file1", O_RDWR | O_CREAT, 0600);   // 文件不存在则创建
        if (fd > 0)
        {
            printf("create file1 success!\n");
        }
    }
    printf("open success: fd = %d\n", fd);

    // ssize_t write(int fd, const void *buf, size_t count);
        // write() 函数原型
    write(fd, buf, strlen(buf));    // strlen() 作用是返回字符串的长度
    //  int close(int fd);
    	// close() 函数原型
    close(fd);

    return 0;
}

3、文件的读取操作

关于光标移动的问题参考链接:[光标移动]

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 *     int open(const char *pathname, int flags);
 *     int open(const char *pathname, int flags, mode_t mode);
 *     int creat(const char *pathname, mode_t mode);
 * */

int main()
{
    int fd = 0;
    char a[] = "Welcom to Linux";
    char *buf = a;
    fd = open("./file1", O_RDWR);
    if (fd == -1)
    {
        printf("open file1 failed\n");
        fd = open("./file1", O_RDWR | O_CREAT, 0600);
        if (fd > 0)
        {
            printf("create file1 success!\n");
        }
    }
    printf("open success: fd = %d\n", fd);

//  ssize_t write(int fd, const void *buf, size_t count);
    int n_write = write(fd, buf, strlen(buf));
    if (n_write != -1)
    {
        printf("write %d byte to file\n", n_write);
    }
//  off_t lseek(int fd, off_t offset, int whence);
//  offset为偏移值,在whence的位置下偏移offset个位置,例如需要把光标移动至开头的第二个位置,令offset为2,whence为SEEK_SET
    lseek(fd,0,SEEK_SET);   // 移动光标至开头


    char *readBuf;          // 读取操作
    readBuf = (char *)malloc(sizeof(char)*n_write + 1); // 申请空间
//  ssize_t read(int fd, void *buf, size_t count);
    int n_read = read(fd, readBuf, n_write);

// readBuf 是文件内容的地址
    printf("read %d ,context:%s\n", n_read, readBuf);

//  int close(int fd);
    close(fd);

    return 0;
}

4、文件光标移动操作

Linux 系统编程_第6张图片

       #include 
       #include 

       off_t lseek(int fd, off_t offset, int whence);

将文件读写指针相对whence移动offset个字节
fd:文件标识符,前面有介绍
offset:对whence的偏移值,如果whence为SEEK_SET,offset为0,则光标指向文件的头。offset为正数右偏移, offset为负数左偏移。
whence :
SEEK_SET:
指向文件的头。
SEEK_CUR:
指向文件的当前光标位置
SEEK_END:
指向文件的尾
原文链接:https://blog.csdn.net/weixin_53414888/article/details/112546513

1)求出文件的字符串长度大小

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int fd = 0;
    
    fd = open("./file1", O_RDWR);
    int file_size = lseek(fd, 0, SEEK_END);   // 返回当前光标的位置,从最后位置返回得到长度
    printf("file size is: %d\n", file_size);
    close(fd);
    
    return 0;
}

5、对文件中的内容进行修改

注意: 运行时是 ./a.out TEST.config

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
	int fd_Src;
    char *readBuf = NULL;
    if(argc != 2)
    {
        printf("param error\n");    // 参数错误
        exit(-1);   // 退出程序
    }
    fd_Src = open(argv[1], O_RDWR);
    int size = lseek(fd_Src, 0, SEEK_END);  // 计算长度
    lseek(fd_Src, 0, SEEK_SET);
    readBuf = (char *)malloc(sizeof(char) * size + 8);  // 申请空间,空间大小比字符串长度大一个字节

    int n_read = read(fd_Src, readBuf, size);   // 读取内容

//  char *strstr(const char *haystack, const char *needle);
    char *P = strstr(readBuf, "LENG = ");    // haystack代表寻找字符串的地址,*needle代表寻找的字符串
    if(P == NULL)   // 说明没找到
    {
        printf("not found!\n");
        exit(-1);
    }
    P = P + strlen("LENG = ");       // 找到之后指针会返回到“LENG = ”中的L
    *P = '5';   // 对“LENG = ”后的内容进行修改,以字符串的形式修改
    
    lseek(fd_Src, 0, SEEK_SET);     // 光标回到开头,然后从开头写入内容
    int n_write = write(fd_Src, readBuf, strlen(readBuf));

    close(fd_Src);

	return 0;
}

6、从键盘上获取数据,并输出数据

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int fd;
    char readBuf[128];
    
/*
	0	标准输入
	1	标准输出
	2	标准错误

*/
	// 读取键盘的输入
    int n_read = read(0, readBuf, 10);		// 标准输入
    int n_write = write(1, readBuf, strlen(readBuf));
    return 0;
}

7、程序实现复制功能(与CP功能一致)

缺点是:复制的内存有限

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
    int fdSrc;
    int fdDes;
    char readBuf[1024];		// 空间大小,1兆

    if(argc != 3)		// 如果变量不够3个既是错误
    {
        printf("pararn error!!!\n");
        exit(-1);
    }
    fdSrc = open(argv[1], O_RDWR);	                   // 打开file1文件
    int n_read = read(fdSrc, readBuf, 1024);		   // 读取file1文件的内容,存放到readBuf中
    fdDes = open(argv[2], O_RDWR | O_CREAT, 0600);	   // 打开file2文件
    int n_write = write(fdDes, readBuf, strlen(readBuf));  // 将readBuf的内容写入file2中

    // 打开了文件就要关闭
    close(fdSrc);	// 关闭file1文件
    close(fdDes);	// 关闭file2文件
    return 0;
}

优化版(防止了内存浪费或者内存溢出)

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
    int fdSrc;
    int fdDes;
    char *readBuf = NULL;	// 定义野指针

    if(argc != 3)
    {
        printf("pararn error!!!\n");
        exit(-1);
    }
    fdSrc = open(argv[1], O_RDWR);
    int size = lseek(fdSrc, 0, SEEK_END);   // 计算字节大小
    lseek(fdSrc, 0, SEEK_SET);              // 把光标移动至开头
    // 申请空间内存
    readBuf = (char *)malloc(sizeof(char) * size + 8);   // 加上8个字节为了防止溢出的可能
    int n_read = read(fdSrc, readBuf, size);	// 有多少字节读取多少字节
    // O_TRUNC 的存在使得先清空再写入数据
    fdDes = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0600);
    int n_write = write(fdDes, readBuf, strlen(readBuf));

    close(fdSrc);
    close(fdDes);
    return 0;
}

8、向file1文件中添加整型数

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    int fd;
    int data = 100;
    int data2 = 0;

    fd = open("./file1", O_RDWR);
    int n_write = write(fd, &data, sizeof(int));
    lseek(fd, 0, SEEK_SET);
    int n_read = read(fd, &data2, sizeof(int));

    printf("read %d \n", data2);
    close(fd);
    
    return 0;
}

向文件中写入一个结构体

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct
{
    int a;
    char c;
}Data;

int main()
{
    int fd;
    /*定义结构体*/
    Data data = {100, 'c'};
    Data data2;   
    
    fd = open("./file1", O_RDWR);
    // 将data数据写入file1文件中
    int n_write = write(fd, &data, sizeof(Data));
    // 光标回到开头
    lseek(fd, 0, SEEK_SET);
    // 将file1中的数据读取到data2中
    int n_read = read(fd, &data2, sizeof(Data));

    // 输出data2的数据
    printf("read %d,%c \n", data2.a, data2.c);
    close(fd);
   
    return 0;
}

fopen函数的用法

#include 
#include 
#include 
int main()
{
//  FILE *fopen(const char *path, const char *mode)
    FILE *fp;
    char *str = "welcom to LINUX!!!";
    char *readBuf = NULL;	// 定义野指针
    fp = fopen("./jian.txt","w+");

//  size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    /**
     * 参数:
     * *ptr    代表 缓冲区地址
     * size    代表 写入长度
     * nmemb   代表 写入字节的长度
     * stream  代表 哪个文件
     */
    fwrite(str, sizeof(char), strlen(str), fp);		// 写入数据(光标在末尾)
//  fwrite(str, sizeof(char)*strlen(str), 1, fp);
    int size = fseek(fp, 0, SEEK_END);			// 获取长度
    fseek(fp, 0, SEEK_SET);				// 移动光标至开头
    readBuf = (char *)malloc(sizeof(char)*size + 8);    // 申请空间内存
//  size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    fread(readBuf, sizeof(char), strlen(str), fp);	// 读取写入的数据

    printf("read data: %s\n", readBuf);			// 输出读取到的数据

    return 0;
}

用 fopen 实现向文件中写入一个结构体数据

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct 
{
    int a;
    char c;
}Test;

int main()
{
    FILE *fp;
    Test data = {100, 'a'};
    Test data2;

    fp = fopen("./file1", "w+");
    int n_write = fwrite(&data, sizeof(Test), 1, fp);
    fseek(fp, 0, SEEK_SET);
    int n_read = fread(&data2, sizeof(Test), 1, fp);

    printf("read %d, %c \n", data2.a, data2.c);
    fclose(fp);

    return 0;
}

fputc()函数的用法

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int main()
{
    FILE *fp;
    int i;
    char *str = "Welcom to Linux!!!";
    int len = strlen(str);	//  字符串获取长度
    fp =  fopen("./file.txt", "w+");	// 打开文件
    for(i = 0; i < len; i++)
    {
    	fputc(*str, fp);		// 写入数据(一次只能写入一个字符)
        str++;
    }

    fclose(fp);
    return 0;
}

feof()函数和fgetc()函数

Linux 系统编程_第7张图片

#include "stdio.h"
#include "stdlib.h"

int main()
{
    FILE *fp;
    char c;
    fp = fopen("./m.c", "r");	// 以只读的方式打开文件

    while(!feof(fp))	// feof() 如果光标在末尾返回非0,否则返回0
    {
        c = fgetc(fp);	// 读取并返回光标处的字符
        printf("%c", c);
    }
    fclose(fp);
    return 0;
}

三、进程

计算机实际上可以做的事情实质上非常简单,比如计算两个数的和,再比如在内存中寻找到某个地址等等。这些最基础的计算机动作被称为指令 (instruction)。所谓的程序(program),就是这样一系列指令的所构成的集合。通过程序,我们可以让计算机完成复杂的操作。程序大多数时候被存储为可执行的文件。这样一个可执行文件就像是一个菜谱,计算机可以按照菜谱作出可口的饭菜。

那么,程序和进程(process)的区别又是什么呢?

从用户角度:进程就是一个正在运行中的程序。
操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块
进程的描述信息有:标识符PID,进程状态,优先级,程序计数器,上下文数据,内存指针,IO状态信息,记账信息。都需要操作系统进行调度。

1、获取进程标识符(非负整数)

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid;
    pid = getpid();		// 获取当前进程标识符
    
    printf("my pid is:%d\n", pid);		// 输出标识符

    return 0;
}

2、由父进程创建子进程

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

Linux 系统编程_第8张图片

getpid()和getppid()这两个函数用来查看当前程序的进程和父进程PID。

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid;
      
    pid = fork();		// 创建子进程
    // 输出
    if(pid > 0)
    {	
    	printf("father printf, pid = %d\n", getpid());
	}
	else if(pid == 0)
	{
		printf("child printf, pid = %d\n", getpid());
	}
/**
 * 程序是父进程运行一次,然后子进程运行一次
 * 第一次是父进程先运行  pid > 0
 * 第二次是子进程运行    pid == 0
 * 故结果会输出两次
 * 
 * fork()相当于创建了一个新的子进程
 * 但是拷贝的是fork()函数之后的所有数据,之前的并不会拷贝。
 * 在代码之上就可以看到parentpid只打印了一次
 * 
 * 总的来说:复制pcb,代码共享,但是子进程并非从头开始,而是从fork()  函数之后开始,数据独有
 */

    return 0;
}

3、对于变量的存储空间的分配

低地址

正文段:代码段,通常是指用来存放 程序执行代码 的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于 只读 ,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些 只读的常数变量 ,例如字符串常量等。程序段为程序代码在内存中的映射.一个程序可以在内存中多有个副本
初始化的数据:函数外已初始化变量的存储空间
未初始化的数据:通常将此段称为bss段,函数外的未初始化变量的存储空间
堆:通常在堆中进行动态存储分配,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)/释放的内存从堆中被剔除(堆被缩减)
栈:又称堆栈, 自动变量以及每次函数调用时所需保存的信息都放在此段中存放程序的 局部变量 (但不包括static声明的变量, static意味着 在数据段中 存放变量)。除此以外,在函数被调用时,栈用来传递参数和返回值。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。储动态内存分配,需要程序员手工分配,手工释放

高地址
Linux 系统编程_第9张图片

原文链接:https://blog.csdn.net/weixin_45775710/article/details/107881495
Linux 系统编程_第10张图片

fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

在早期的 Linux 中子进程会把父进程的所有程序存储空间都会拷贝一份
如今利用了“写实操作”,当子进程不改变变量时,子进程和父进程共享变量空间。如果子进程对变量进程了改变,才会开辟空间给变量

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid;    
    pid = fork();
    int data = 10;		// 定义变量

    if(pid > 0)
    {
        printf("this is father printf, pid = %d\n", getpid());
    }
    else if(pid == 0)
    {
        data = 100;		// 在父进程中不改变变量的值,在子进程中改变
        printf("this is child printf, pid = %d\n", getpid());
    }
    printf("data = %d\n", data);	// 输出变量
    return 0;
}

/**
 * 输出结果:
 * this is father printf, pid = 11824
 * data = 10 
 * this is child printf, pid = 11825
 * data = 100;
 */

4、创建子进程的目的

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段,在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

比如:
有一天家里要办酒席
父亲一大早就在门口等待客人的到来
当客人来到了,拿出请帖给父亲核查
父亲看请帖后说:“这是自己邀请的客人”
然后让儿子接待客人,父亲接着等待客人……

举例:不断的检测用户的输入,当用户输入1的时候创建子进程,做一些事情

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid;
    int data;

    while (1)
    {
        printf("please input a data \n");
        scanf("%d", &data);
        if (data == 1)
        {
            pid = fork();
            if (pid > 0)
            {
            	// 什么都不做
            }
            else if (pid == 0)
            {
                while (1)
                {
                    printf("do net request, pid = %d\n", getpid());
                    sleep(3);
                }
            }
        }
        else
        {
            printf("wait, do nothing!!!\n");
        }
    }
    return 0;
}

/**
 * 第一次输入data如果不等于1就输出 wait, do nothing!!!
 * 如果输入的data是等于1 创建子进程,父进程继续等待输入
 * 一个父进程可以创建多个子进程
 */

5、Linux-fork创建子进程

A:为什么要创建子进程

(1)每一次程序运行都需要一个进程

(2)多个进程实现宏观上的并行

B:fork的内部原理

(1)进程的分裂生长模式。

如果操作系统需要一个新进程来运行一个程序,那么操作系统会用一个现有的进程来复制生成一个新的进程。

(2)fork函数调用一次会返回两次,返回值等于0的就是子进程,而返回值大于0的就是父进程。

(3)典型的使用fork的方法:使用fork后然后用if判断返回值,并且返回值大于0时就是父进程,等于0时就是子进程。

(4)fork的返回值在子进程中等于0,在父进程中等于本次fork创建的子进程的进程ID。

C:关于子进程

(1)子进程有自己独立的PCB

(2)子进程被内核同等调度

D:父子进程对文件的操作

(1)子进程继承父进程中打开的文件

a、父进程先open打开一个文件得到fd,然后在fork创建子进程。之后在父子进程中各自write向fd中写入内容

b、测试结论:接续写。实际上本质原因是父子进程之间的fd对应的文件指针是彼此关联的(特别像O_APPEND标志后的样子)

E:父子进程各自独立打开同一文件实现共享

(1)父进程open打开1.txt然后写入,子进程打开1.txt然后写入,结论是:分别写。

原因是父子进程分离后才各自打开的1.txt,这个时候两个进程的PCB已经独立了,文件表也独立了,因此2次读写是完全独立的。

(2)open时使用O_APPEND标志后,实际测试结果表明O_APPEND标志可以把父子进程各自独立打开的fd的文件指针给关联起来,实现分别写。

F:总结:

(1)父进程在没有fork之前自己做的事情对子进程有很大影响,但是父进程在fork之后,在自己的if里做的事情就对子进程没有影响了。本质原因就是因为fork内部实际上已经复制父进程的PCB生成一个新的子进程,并且fork返回时子进程已经完全和父进程脱离并且独立的被操作系统OS调度执行了。

(2)子进程的最终目的是要独立的去运行另外的程序。

6、fork()函数和vfork()函数的区别

关键区别一:
vfork直接使用父进程存储空间,不拷贝。
父进程和子进程共享存储空间,如果子进程改变了全局变量的值父进程的值也会改变。
关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。
如果不进行exit退出则会一直运行子进程。

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid;    
    pid = vfork();

    if(pid > 0)
    {
        while(1)
        {
            printf("this is father printf, pid = %d\n", getpid());
            sleep(3);
        }
    }
    else if(pid == 0)
    {
        while(1)
        {
            printf("this is child printf, pid = %d\n", getpid());
            sleep(3);
        }    
    }

    return 0;
}
/**
 * 程序会一直留在子进程中,只有子进程结束时才会运行父进程
 */

7、用 exit() 函数来退出子进程

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"

int main()
{
    pid_t pid;    
    pid = vfork();
    int i; 
   
    if(pid > 0)
    {
        while(1)
        {
            printf("this is father printf, pid = %d\n", getpid());
            sleep(1);
            printf("i = %d\n", i);
        }
    }
    else if(pid == 0)
    {
        while(1)
        {
            printf("this is child printf, pid = %d\n", getpid());
            sleep(1);
            i++;
            if(i == 3)
                exit(0);
        }    
    }

    return 0;
}


/**
 * 子进程中的变量 i 自加到3时结束子进程,然后父进程开始运行
 */

8、wait等待进程

在子进程exit退出中如果不进行等待,子进程会变成僵尸进程

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"

int main()
{
    pid_t pid;    
    pid = fork();
    int i; 
   
    if(pid > 0)		// 父进程
    {
        // wait(NULL); 代表不关心子进程的返回状态,仅仅是等待子进程退出
        wait(NULL);	// 等待子进程运行结束,防止子进程变成僵尸进程
        while(1)
        {		// Z+ 代表僵尸进程,S+代表真正运行的进程
            printf("this is father printf, pid = %d\n", getpid());
            sleep(1);
            printf("i = %d\n", i);
        }
    }
    else if(pid == 0)	// 子进程
    {
        while(1)
        {
            printf("this is child printf, pid = %d\n", getpid());
            sleep(1);
            i++;
            if(i == 5)
                exit(0);	// 子进程退出状态不被收集,变成僵死进程(僵尸进程)
        }    
    }

    return 0;
}


/**
 * 子进程中的变量 i 自加到3时结束子进程,然后父进程开始运行
 */

有参wait(&status)返回子进程退出状态

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"

int main()
{
    pid_t pid;    
    pid = fork();
    int i;
    int status = 502;
   
    if(pid > 0)		// 父进程
    {
        wait(&status);	// 非空,返回子进程退出状态 即如果用exit(3)退出就返回3给status
        printf("child quit, status = %d\n", WEXITSTATUS(status));	// 必须用WIFEXITED修饰
        while(1)
        {		// Z+ 代表僵尸进程,S+代表真正运行的进程
            printf("this is father printf, pid = %d\n", getpid());
            sleep(1);
            printf("i = %d\n", i);
        }
    }
    else if(pid == 0)	// 子进程
    {
        while(1)
        {
            printf("this is child printf, pid = %d\n", getpid());
            sleep(1);
            i++;
            if(i == 5)
                exit(3);	// 子进程退出状态不被收集,变成僵死进程(僵尸进程)
        }    
    }
    return 0;
}

/**
 * 子进程中的变量 i 自加到3时结束子进程,然后父进程开始运行
 */

9、孤儿进程

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "stdlib.h"
#include "sys/wait.h"

/**
 *	父进程如果不等待子进程退出,在子进程之前结束自己的“生命”,此时子进程叫做孤儿进程
 *	Linux避免系统存在过多孤儿进程,init进程收留孤儿进程。所以getppid()返回的是1
 *
 */

int main()
{
    pid_t pid;    
    pid = fork();
    int i;
    int status = 502;
   
    if(pid > 0)		// 父进程
    {
            printf("this is father printf, pid = %d\n", getpid());	// 仅仅运行一次
    }
    else if(pid == 0)	// 子进程
    {
        while(1)
        {
            printf("this is child printf, pid = %d, my father pid = %d\n", getpid(), getppid());
            sleep(1);
            i++;
            if(i == 5)
                exit(3);	// 子进程退出状态不被收集,变成僵死进程(僵尸进程)
        }
    }

    return 0;
}


/**
 * 子进程中的变量 i 自加到3时结束子进程,然后父进程开始运行
 */

10、exec群函数

参考博文

exec族函数函数的作用:
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。

功能:
  在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
函数族:
  exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
函数原型:

#include 
extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

返回值:
  exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

以execl函数为例子来说明:

//文件execl.c
#include 
#include 
#include 
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("./bin/echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!\n");
        perorr("Why");		// 用来输出错误信息      
    }
    printf("after execl\n");
    return 0;
}
//文件echoarg.c
#include 

int main(int argc,char *argv[])
{
    int i = 0;
    for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]); 
    }
    return 0;
}

实验结果:

在这里插入代码片ubuntu:~/test/exec_test$ ./execl
before execl****
argv[0]: echoarg
argv[1]: abc

实验说明:
我们先用gcc编译echoarg.c,生成可执行文件echoarg并放在当前路径bin目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg,将当前进程main替换掉,所以”after execl” 没有在终端被打印出来。

你可能感兴趣的:(Linux,linux,运维,服务器)