Linux环境高级编程基础

2020-08-11  本文已影响0人  azmohan

Linux是最受程序员欢迎的操作系统之一。第一它是开源的,第二它的系统调用少,第三它的抽象更到位。一切皆进程,一切皆文件。这两个“一切”已经把Linux的基调表达的充分无疑。统一接口就是对用户最大的友善。我想没有一个程序员愿意学习动辄就上千个系统调用,还不知道是怎么实现的操作系统。

进程及线程出现的背景

如果想要深入理解一个事物的本质,最好的方式就是去追寻这个事物出现的历史背景和推动因素。

有了线程,操作系统调度的最小单元就变成了线程,而进程变成了操作系统分配资源的最小单元。有了多核CPU,任务执行也不再是分时系统,多个线程可以同时在多个CPU上运行,此时才做到时间上的真正并行,目前操作系统处理多核CPU的方案最主要是SMP(Symmetric Multi-Processor对称多处理器结构)。

linux进程

1. 进程的创建
linux使用fork创建一个进程。linux系统运行起来后会创建一个为编号为1的init的进程。后面的进程都是init进程的后代。

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

int main(int argc,char** argv) {
    int pid = fork();
    printf("pid = %d\n",pid);
    if (pid == 0) {
        printf("pid = %d,my parent pid = %d\n",getpid(),getppid());
    } else {
        printf("pid = %d,my parent pid = %d\n",getpid(),getppid());
        wait(NULL);
    }
    return 0;
}

这个函数为返回两次,如果0返回给子进程,如果大于0返回给父进程子进程的pid。

2. 进程运行状态机

image.png

3.进程回收

回收原则:谁创建谁回收,回收不了init进程负责。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

4.进程在内核本质
进程在内核中的形态就是task_struct,主要数据如下:

struct task_struct{
#ifdef CONFIG_THREAD_INFO_IN_TASK
    /*
     * For reasons of header soup (see current_thread_info()), this
     * must be the first element of task_struct.
     */
    struct thread_info      thread_info;
#endif
    /* -1 unrunnable不可运行, 0 runnable可运行, >0 stopped已经停止: */
    /* 进程状态TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_STOPPED、TASK_TRACED */
    volatile long           state;
/* 进程标识符,相当于每一个学生的学号一样,标识符唯一标识进程 */
    pid_t               pid;
    
    /* P所在进程组的领头进程的PID */
    pid_t               tgid;
/* Real parent process: */
    /* 指向创建了P的进程的描述符,如果P的父进程不再存在,就指向进程1(init)的描述符(因此,如果用户运行一个后台进程而且退出了shell,后台进程就会成为init的子进程 */
    struct task_struct __rcu    *real_parent;

    /* Recipient of SIGCHLD, wait4() reports: */
    /* 指向P的当前父进程(这种进程的子进程终止时,必须向父进程发信号)。它的值通常与real_parent一致,但偶尔也可以不同,例如,当另一个进程发出监控P的ptrace()系统调用请求 */
    struct task_struct __rcu    *parent;

    /*
     * Children/sibling form the list of natural children:
     */
    /* 链表的头部,链表中的所有元素都是P创建的子进程 */
    struct list_head        children;

    /* 指向兄弟进程链表中的下一个元素或前一个元素的指针,这些兄弟进程的父进程都是P */
    struct list_head        sibling;
    
    /* P所在进程组的领头进程的描述符指 */
    struct task_struct      *group_leader;
/* 用来表示进程与文件系统的联系,包括当前目录和根目录 Filesystem information: */
    struct fs_struct        *fs;

    /* 表示进程当前打开的文件 Open file information: */
    struct files_struct     *files;
#ifdef CONFIG_NUMA
    /* Protected by alloc_lock: */
    struct mempolicy        *mempolicy;
    short               il_prev;
    short               pref_node_fork;
#endif
}

5. exec家族
一段运行或是活的代码就是进程,其实这种说法是不精确的。更准确的说是系统先创建了进程,然后让进程去运行我们写得代码。只不过可执行文件在运行时系统先给我们创建了进程。可以使用exec模拟这一场景。

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

int main(int argc,char** argv) {
    int pid = fork();
    if (pid == 0) {
        printf("I am child my pid = %d,my parent pid = %d\n",getpid(),getppid());
        execv("./task",NULL);
    } else {
        wait(NULL);
    }

    return 0;
}

以上代码就是先创建一个进程,然后执行当前目录的task任务。

线程

使用pthread可以操作线程,对linux内核来说是不区分进程和线程的,它也是task_struct,只不过把内存、文件系统等资源设置成了共享的。当然进程比线程更重一些,更耗资源一些,其实linux的很多应用是使用进程实现,因为相对其它系统它本身就是轻量级的。这也是linux宣称的一切皆进程。

创建线程

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

int a = 3;
void * thread_handle_fun(void * args) {
    printf("thread_handle_fun a = %d\n",a);
    printf("current thread = %d\n",pthread_self());
    printf("my pid = %d\n",getpid());
    a = 4;
    printf("thread_handle_fun modify a\n");
    return NULL;
}

int main(int argc,char** argv) {
    pthread_t pt;
    if (pthread_create(&pt,NULL,thread_handle_fun,NULL) == -1) {
        perror("create error!");
        return -1;
    }
    printf("main current thread = %ul\n",pthread_self());
    printf("main my pid = %d\n",getpid());

    if (pthread_join(pt,NULL)) {
        perror("thread is not exit");
        return -1;
    }
    printf("main a = %d\n",a);
    return 0;
}

注:当一个线程正常退出对宿主进程没有影响,但是如果一个线程异常退出了,宿主进程也会跟着挂。这也是安全要求较高的应用大多使用进程,而不是线程的原因。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

void * thread_handle_fun(void * args) {
    printf("thread_handle_fun modify a\n");
    for(int i = 0;i < 5;i++) {
        sleep(1);
        printf("i = %d\n",i);
        if (i ==3) {
            pthread_exit(NULL);
            //int *array = (int*) args;
            //array[0] = 1;
        }
    }
    return NULL;
}

int main(int argc,char** argv) {
    pthread_t pt;
    if (pthread_create(&pt,NULL,thread_handle_fun,NULL) == -1) {
        perror("create error!");
        return -1;
    }

    if (pthread_join(pt,NULL)) {
        perror("thread is not exit");
        return -1;
    }
    while(1) {
        sleep(1);
    }
    return 0;
}

Linux文件

Linux 中所有内容都是以文件的形式保存和管理的,即一切皆文件,普通文件是文件,目录是文件,硬件设备(键盘、监视器、硬盘、打印机)是文件,就连套接字(socket)、网络通信等资源也都是文件。

Linux系统中,文件具体类型: 普通文件 、目录、字符设备和块设备(/dev)、套接字文件(socket)(/var/run/)、符号链接文件(symbolic link)、管道文件(pipe)。

WeChat6cabeb442e842f871ee8b49a06eef941.png

1. 对文件的基本操作
libc操作代码

#include<stdio.h>
#include<string.h>

int main(int argc,char** argv) {
    FILE * pfile = fopen("1.txt","w+");
    char array[] = "this is a test of operating file";
    char buffer[50];
    fwrite(array,strlen(array) + 1,sizeof(char),pfile);
    fseek(pfile,0,SEEK_SET);
    fread(buffer,strlen(array) + 1,sizeof(char),pfile);
    puts(buffer);
    fclose(pfile);
    return 0;
}

系统调用操作代码

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main(int argc,char** argv) {
    int fd = open("1.txt",O_RDWR);
    char str[] = "this is a test of operating file using system call.";
    char buffer[50];
    if (fd > 0) {
        printf("sizeof(str) = %lu\n",sizeof(str));
        printf("strlen(str) = %lu\n",strlen(str));
        write(fd,str,sizeof(str));
        lseek(fd,0,SEEK_SET);
        read(fd,buffer,sizeof(str));
        puts(buffer);
        close(fd);
    }
    return 0;
}

一切皆文件kernel实现

一切皆文件的基本哲学是要对用户提供统一的操作界面或接口。内核的虚拟文件系统(VFS)提供此功能的支持。给用户空间的程序提供统一文件系统接口,给驱动程序提供统一的规约,允许不同的文件系统共存。

https://blog.csdn.net/mignatian/article/details/81673713

WeChataa736f04effbf06bfffc88f19efb9c27.png

字符设置驱动

https://www.cnblogs.com/chen-farsight/p/6155518.html

推荐阅读

https://cloud.tencent.com/developer/article/1507511

上一篇下一篇

猜你喜欢

热点阅读